赛后看了下别人的讨论恍然大悟。这题比赛的时候一直纠结着1e9的预处理会爆内存已经1e9的即时查询会tle。毕竟还是Too naive, 觉得数列题就只能有通项式, 或者是递推预处理, 完全没有想到预处理中间的过程采用公式能够大量节省时间以及空间Orz….
做法,首先这道题answer序列为 1, 5, 11, 23, 38, 62, 90… 经过找规律我们能发现ans[i] = Σ a[j] (1 <=a[j] <=i) 蓝儿这个规律比赛时早就推出来了, 却一直卡在了求和上。
下面是解法, 对于原数组a[i] = 1, 2, 2, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 6…… 我们把每个数组最后一次出现的位置记作end[x], 比如end[1] = 1, end[2] = 3, end[3] = 5… 然后我们可以在原数组end[x - 1] + 1 ~ end[x]的位置的这个值 ( 对, 是值,比如end[2] + 1 ~ end[3]的位置的值∈{4, 5} ) 这个值在求和最后的答案中被累加了x次 ( 之前的出现在end[2] + 1 ~ end[3]中的4和5 累加的值就是 4+4+4+5+5+5 = (4 + 5) * 8 , 即 end[x - 1] + 1 ~ end[x] 等差数列求和 * x ) Orz… 因为差不多end[44w+] > 1e9, 于是这里的累加部分改成相乘 预处理复杂度直接从1e9变成了 40多w,Orrrrrrrrrrrrrrz ….
下面是代码
#include <bits/stdc++.h>
using namespace std;
const int p = 1000000007;
int a[500000];
int b[500000], k[500000];
vector <int> w;
long long get(int l, int r) {
return 1ll * (l + r) * (r - l + 1) / 2;
} // l到r的等差数列求和
int main() {
a[1] = 1; a[2] = a[3] = 2;
int M = 3;
for (int i = 3;; i++) {
for (int j = 0; j < a[i]; j++) {
a[++M] = i;
}
if (M > 450000) break;
}
int x = 0, d = 1, c;
long long ans = 0;
w.push_back(0); //这里的w就是end数组
while (1) {
c = a[d];
ans = (ans + get(x + 1, x + c) * d) % p;
x += c; d++; c = a[d];
b[w.size()] = ans;
k[w.size()] = d;
w.push_back(x);
if (x + c > p) break; //题目中给的mod 1e9+7 差不多就和n的上限差不多
}
k[0] = 1;
int T;
scanf("%d", &T);
while (T--) {
int n;
scanf("%d", &n);
int pos = lower_bound(w.begin(), w.end(), n) - w.begin() - 1;
//用二分求出比满足end[pos] < n 的最大的pos
int x = w[pos]; long long ans = b[pos];
if (x < n) ans = (ans + get(x + 1, n) * k[pos]) % p;
cout << ans << endl;
}
}