hdu 5439 Aggregated Counting(找规律)

题目大意:

一个序列1、2、2、3、3、4、4、4、5、5、5……

第i项的数a[i]表示连续的i的个数,依次下去。

现给出n(n<=1e9),记最后出现n的位置为last[n],求last[last[n]]。


规律:last[last[n]]=a[1]+a[2]+……+a[last[n]]。比如last[3]=5,则last[last[3]]为序列的前5项的和,为11

知道了这个还不能算,因为n太大了。

再对上式进行归纳整理,可以得到:

last[last[n]]=1*1+(2+3)*2+(4+5)*3+……+(k+k+1+k+2+……+a[last[p-1]])*(p-1)+(a[last[p- 1]]+1+……+n)*p

其中每一个括号里面的数字构成等差序列,第i个括号里面的项数恰好是a[i](最后一个括号除外,最多只加到n),最后一项为a[last[i]]。


n最大可达到1e9,而p的最大值相对而言会小得多。

通过测试可以知道最大的p不会超过500000(事实上只有43万多一点)

那么,可以预处理出500000以内的序列a[i],last[i]。

以及前缀和ans[i]。

这里的ans[i]=1*1+(2+3)*2+(4+5)*3+……+(k+k+1+k+2+……+a[last[i])*i

对于每一个n,通过二分查找,找到p(其前面的括号项数刚好是a[i]的最大的p),即可马上得出答案。




#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
#define maxn 500000
#define mod 1000000007
typedef __int64 LL;
int a[maxn],last[maxn],tot;
LL ans[maxn];
void init()
{
    int i;
    a[1]=last[1]=1;
    a[2]=2;
    last[2]=3;
    a[3]=2;
    LL sum=3;
    for(i=3;sum<1000000000;++i){
        sum+=a[i];
        last[i]=sum;
        for(LL j=sum-a[i]+1;j<=min(sum,(LL)maxn);++j) a[j]=i;
    }
    tot=i-1;
    ans[0]=0;
    for(i=1;i<maxn;++i)
        ans[i]=(ans[i-1]+(LL(i)*(last[i]-a[i]+1+last[i])%mod)*a[i]%mod*500000004%mod)%mod;
}

int Find(int n)
{
    int l=1,r=tot,ans;
    while(l<=r)
    {
        int m=(l+r)>>1;
        if(last[m]<=n) {ans=m;l=m+1;}
        else r=m-1;
    }
    return ans;
}

int main()
{
    init();
    int T,n;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        int p=Find(n);
        LL res=ans[p];
        if(last[p]<n) res=(res+LL(p+1)*(last[p]+1+n)%mod*(n-last[p])%mod*500000004)%mod;
        printf("%I64d\n",res);
    }
    return 0;
}




你可能感兴趣的:(hdu 5439 Aggregated Counting(找规律))