您的位置:首页 > 其它

NOIP模拟赛 t3 nan

2017-07-07 18:43 351 查看
模拟赛的名字都好迷啊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 long
using namespace std;

const int N = 1400000 + 10;
const int mod = 1e9 + 7;

int t;
ll left
,right
;

int temp;//有多少组
ll ans=0;

ll sum
;//求前缀和

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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: