机房练习赛nan(分块压缩)

机房练习赛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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值