NOIP模拟赛 t3 nan

来源:互联网 发布:lego ev3 编程 编辑:程序博客网 时间:2024/06/05 10:07

模拟赛的名字都好迷啊23333
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))%(10^9+7) 的值。
【样例输入】
3
3
10
100000
【样例输出】
11
217
507231491
【样例解释】
╭︿︿︿╮
{/ o o /}
( (oo) )
︶︶︶
【数据规模与约定】
对于30% 的数据, 1<=x<=10^3。
对于60%的数据, 1<=x<=10^6。
对于100%的数据,1<=x<=10^9,1<=T<=2*10^3 。

考试的时候看到这道题内心窃喜,以为自己打表就可以过60%了。
然后。
数组简直怎么开都开不够啊,too large。最后还是只打了30分的表。
于是想尽了办法优化暴力(…)结果最后打表的程序根本交不上,早知道就把暴力交上去了还有30分。

题目分析:
如果对每个数x,直接存每个数的last,甚至是last(last(x)),无疑都是会爆空间的。考虑分区间来做。
对于原序列:
1,2,2,3,3,4,4,4,5,5,5,6,6,6,6,7,7,7,7,…
last[i]=1+2*2+3*2+…+(i-1)*num[i-1]+num[i];
其中num[i]表示这个数出现的次数
对于每个数字出现的次数再确定一个序列:
1,2,2,3,3,4,4,4,5,…
是不是特别神奇。。。
我们可以把出现次数相同的数划在同一个区间,则原序列可以划分为:
1|2,2,3,3,|4,4,4,5,5,5,|6,6,6,6,7,7,7,7,…
对第i个区间,数字出现的次数为i。
last[last[i]]其实就相当于求第二个序列(划分区间)后的last[i]。
[据大佬说暴力算一下最后跑1e9大概一百四十万个区间就够了,这样数组和时间复杂度都是可以接受的]

怎么求啊?
对于每个区间利用两个数组left,right,分别存储区间最左边的数和最右边的数。不难发现区间i最右边的数就是last[i]。
而last[i]其实可以转化为求和了,而且是等差求和。
我们可以维护一个前缀和表示区间1到区间i的和。但算的是每个区间,要回到原序列就还要再乘个数。
而输入的数n很有可能是在区间中间,而不是卡在区间的两端。
所以我们还要对n所在的区间里的和再处理。先二分找到n所在的区间,然后再利用等差求和处理区间内部。

为什么前缀和sum[i]对应的就是last[last[i]]?
还是看这个序列:
1,2,2,3,3,4,4,4,5,5,5,6,6,6,6,…
还是举栗子吧:last[3]=5,
而在num[i]的序列中:
(last[i]=sum[i])
1,2,2,3,3,4,4,4,5,5,5,6,6,6,6,…
5对应的那个数又是第二次出现的3 (这个是显然的,对任意都成立,根据这个数列的形成方式)
所以last[5]=sum[3];
所以last[last[i]]=sum[i]

具体看代码吧。

#include<cstdio>#include<cstring>#include<algorithm>#define ll long longusing namespace std;const int N = 1400000 + 10;const int mod = 1e9 + 7;int t;ll left[N],right[N];int temp;//有多少组 ll ans=0;ll sum[N];//求前缀和 int main(){    //freopen("nan.in","r",stdin);    //freopen("nan.out","w",stdout);    left[1]=1,right[1]=1;    left[2]=2,right[2]=3;    temp=2;    for(int i=3;i<=N - 10;i++){        left[i]=right[i-1]+1;        right[i]=right[i-1]+temp;        if(i>=right[temp]) temp++;//已经到下一组了             }    memset(sum,0,sizeof(sum));    for(int i=1;i<=N - 10;i++){        sum[i]=sum[i-1]+i*(right[i]-left[i]+1)*(left[i]+right[i])/2%mod;        sum[i]%=mod;    }    scanf("%d",&t);    while(t--){        int n;        scanf("%d",&n);        int i=lower_bound(left+1,left+N-9,n)-left-1;        //二分查找是第几个区块        ans=sum[i-1];        ans+=(i)*(n-right[i-1])*(left[i]+n)/2%mod;        printf("%I64d\n",ans%mod);     }    return 0;}
原创粉丝点击