机房练习赛nan(分块压缩)
【问题描述】
我们 有一个序列 ,现在他里面有三个数 1,2,2。我们从第三个数开始考虑:
1、第三个数是 2,所以我们在序列后面写 2个3,变成 1,2,2,3,3。
2、第四个数是 3,所以我们在序列后面写 3个4,变成 1,2,2,3,3,4,4,4。
那么你可以看到 ,这个序列应该是 1,2,2,3,3,4,4,4,5,5,5,6,6,6,6,…。
如果我们设一个数 如果我们设一个数 如果我们设一个数 如果我们设一个数 如果我们设一个数 如果我们设一个数 如果我们设一个数 如果我们设一个数x最后出现的位置为last(x),那么现在我希望知道last(last(x))等于多少 。
【输入格式】
第一行 一个整数T,代表数据组数。
接下来T行每行一个整数x。
【输出格式】
T行,每行一个整数 ,代表last(last(x)) mod (109+7)的值 。
【样例输入】
3
3
10
100000
【样例输出】
11
217
507231491
【数据规模与约定】
对于 30%的数据, 1≤N≤10³。
对于 60%的数据 ,1≤N≤106。
对于 100%的数据 ,1≤N≤109,1≤T≤2×10³。
思路(部分摘录):
首先打表观察last和last(last) (这里只写10组):
x _____________1 2 3 4 5 6 7 8 9 10
a[x]___________1 2 2 3 3 4 4 4 5 5
last(x)________1 3 5 8 11 15 19 23 28 33
last(last(x))___1 5 11 23 38 62 90 122 167 217
考虑计算last(last(x))-last(last(x-1)):
不要问怎么想到的,不断尝试,狗眼观察。
last(last(x))-last(last(x-1))
1 4 6 12 15 24 28 32 45 50
又可以写成:
1 * 1 2 * 2 2 * 3 3 * 4 3 * 5 4 * 6 4 * 7 4 * 8 5 * 9 5 * 10
可以看出,后面的数1~10就等于i,而前面的数刚好就是a[x]
于是我们考虑维护这个差的序列,我们知道了f[2]-f[1],f[3]-f[2],f[4]-f[3]……f[n]-f[n-1]把他们加起来,再加一个f[1]就可以得到f[n]了(f[x]就是last(last(x)))。之后的分块等操作就全部是在f上维护,与原序列无关了。
于是预处理的时候就将各个分块(a[x]相同的为一块)的ll和rr保存块的左右位置,这样提取公共部分之后用等差序列求和就能得出ans了
至于n在中间的情况(如last(last(7)),就用a[4]的right和n(7)做比较,n小则只加上left到n的等差序列,这样就可以过60%的数据
至于100%的数据,要用到前缀和和二分的方法:前缀和求前面连续分块的和,二分求出离n最近的a[r].rr的编号rightn,
再加上后面的等差序列(同上),可以算出大约第130万个分块的长度超过了1e9,所以二分是可行的。
还有其他的不同分块方法。
#include<cstdio>
#include<iostream>
#include<cmath>
#define LL long long
using namespace std;
const LL N = 1400000+7;
const LL mod = 1e9+7;
int n, t;
LL pos[N], sum[N];
struct node{
LL ll, rr;//左右边界位置
}a[N];
int main(){
freopen("nan.in", "r", stdin);
freopen("nan.out", "w", stdout);
pos[1] = 1, pos[2] = 3; int cnt = 2; LL temp = 2;
a[1].ll = a[1].rr = 1;
a[2].ll = 2; a[2].rr = 3;
for(int i=3; i<=N; i++){
pos[i] = pos[i-1] + cnt;
a[i].rr = pos[i];
a[i].ll = pos[i-1] + 1;
if(i >= pos[temp]){
cnt++; temp++;
}
}
for(int i=1; i<=N; i++){
sum[i] = sum[i-1] + (a[i].rr-a[i].ll+1) * i * (a[i].ll+a[i].rr) / 2 % mod;
sum[i] %= mod;//块内,提出共有的然后用等差算
}
scanf("%d", &t);
LL ans = 0;
for(int i=1; i<=t; i++){
int n; scanf("%d", &n);
int l = 1, r = N;
while(l <= r){//二分在哪一块中
int mid = (l + r) >> 1;
if(a[mid].rr < n) l = mid + 1;
else r = mid - 1;
}
ans = sum[r];
ans += (r+1) * (n-a[r].rr) * (a[r+1].ll+n) / 2 % mod;//剩余的用等差算
printf("%I64d\n", ans % mod);
}
return 0;
}