(找规律)
题意:
给定一个数列,以及
T
T
()个询问。每个询问包含一个数字
n
n
(),输出这个数列的前
n
n
项和。
思路:
先打表,看看和
Sn
S
n
(将数列的前n项和记为
Sn
S
n
)有什么规律。
n
n
: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
: 1, 1, 2, 3, 3, 4, 4, 4, 5, 6
Sn
S
n
: 1,2,4,6,9,13,17,21,26,32
观察到
an
a
n
中的数字是连续出现的,但是每个数字出现次数不同。这时找不到
an
a
n
数字出现次数和n的关系。但是发现
an
a
n
出现次数和自身这个数字有关:
an
a
n
出现的次数等于
an
a
n
的lowbit值的位数。(比如3,它的二进制是1 1,lowbit只有一位,故3出现一次)。
这样一来,虽然给定
n
n
的值,我们不能找到,但是反过来是可以运算的。举例而言,假如
an
a
n
为5,
n
n
的值就是(通过枚举
lowbit(x)
l
o
w
b
i
t
(
x
)
的值来求解)。知道这个以后,我们可以准确估计一下
an
a
n
的上下界,然后通过二分求得
an
a
n
。时间复杂度:
O(K∗log(n))
O
(
K
∗
l
o
g
(
n
)
)
。
求出
an
a
n
后,就想办法求
Sn
S
n
,由于
an
a
n
之前的数字肯定小于等于
an
a
n
,并且是连续出现。我们就可以想办法将这个数列拆成
k
k
个子数列,进而再求和。其中第个子数列中的数字的
lowbit
l
o
w
b
i
t
值都是
i
i
。时间复杂度:
总时间复杂度:
O(K∗log(n))
O
(
K
∗
l
o
g
(
n
)
)
关键点:
想到如何快速找到
an
a
n
的值。
代码:
#include <cstdio>
#include <algorithm>
#define LL long long
using namespace std;
const LL mod = 1000000007;
const LL inv2 = 500000004;
LL get_idx(LL num) {
LL ret = 0, x = num;
for (LL i=0; (1LL<<i)<=num; i++) {
ret += (i+1) * ((x+1)>>1);
x /= 2;
}
return ret;
}
LL get_sum(LL num) {
LL ret = 0;
for (LL i=0; (1LL<<i)<=num; i++) {
LL n_term = ((num-(1LL<<i)) >> (i+1)) + 1;
LL fst = (1LL<<i), lst = fst + ((n_term-1) << (i+1));
LL incre = (i+1) * ((fst + lst) % mod) % mod * (n_term % mod) % mod * inv2 % mod;
//ret += (i+1) * (fst + lst) * n_term / 2;
ret = (ret + incre) % mod;
}
return ret;
}
int main() {
//freopen("in.txt", "r", stdin);
LL n, T;
scanf("%lld", &T);
while (T --) {
scanf("%lld", &n); n --;
LL l = max(n/2-50, 1LL), r = n/2+50;
while (l < r) {
LL mid = (l+r) / 2, idx = get_idx(mid);
if (idx < n) l = mid + 1;
else r = mid;
}
LL sum = get_sum(l), cur_idx = get_idx(l);
LL offset = l % mod * (cur_idx - n) % mod;
LL ans = (sum - offset + mod + 1) % mod;
printf("%lld\n", ans);
}
return 0;
}