HDU

http://acm.hdu.edu.cn/showproblem.php?pid=6304

题意

给出一个数列的定义,a[1]=a[2]=1,a[n]=a[n-a[n-1]]+a[n-1-a[n-2]](n>=3)。求前n项和,n<=1e18。

分析

一看就是得打表找规律或推公式的题目。

先把a[i]打出来: 1 1 2 2 3 4 4 4 5 6 6...

乍眼一看每个数字出现的次数有点意思,于是打出每个数出现次数:

数值   1  2  3  4  5  6  7  8  9  10  11  12  13  14  15  16

次数   2  2  1  3  1  2  1  4  1   2    1    3    1    2    1    5

感觉第一个1很不和谐啊,先忽略这个1看看:

数值   1  2  3  4  5  6  7  8  9  10  11  12  13  14  15  16

次数   1  2  1  3  1  2  1  4  1   2    1    3    1    2    1    5

可以看到前2^i个数的出现次数是由前2^(i-1)个数复制两次,并把2^i的次数+1得到的。

这样就得到数值出现次数的规律了,设cnt[i]为前2^i个数的次数之和,那么cnt[i]=2*cnt[i-1]+1。

有了cnt[i],对于一个下标n,可以求出a[n]的值,相反也可以求出值为a[n]的第一个位置。

然后怎么求前n项和呢?把相同出现次数的值输出看看:

1-- 1,3,5,7,9....

2-- 2,6,10,14...

3-- 4,12,20,28...

4-- 8,24,40,56...

....

很明显的规律,对于次数k,对应数值形成一个首项为2^(k-1),公差为2^k的等差数列。这个等差数列的每个值都出现k次。

所以,可以枚举次数,计算以a[n]为上界的项数,再把这个等差数列的和*次数加到答案中。

需要注意,计算等差数列时不能把a[n]算进去,因为a[n]出现的次数在n的限制下是不完全的,需要另外计算,这时就用到上面计算的a[n]出现的第一个位置了,由此算出a[n]实际出现的次数,再加到答案中。

由于数据是ll级别,出现相乘时不要忘记先模一下。

其它细节看代码。

#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
const ll mod = 1e9 + 7;

ll cnt[64],p[64];
//预处理2^i和cnt[i]
void init(){
    cnt[0]=p[0]=1;
    for(int i=1;i<=62;i++) cnt[i]=2*cnt[i-1]+1,p[i]=2*p[i-1];
}
//计算a[n]的数值
ll caln(ll n){
    if(n==1) return 1;//特殊处理
    n--;//由于规律从实际的第二个开始计算
    ll an = 0;
    for(int i=62;i>=0;i--){
        while(cnt[i]<=n){
            n-=cnt[i];
            an+=p[i];
        }
    }
    return an;
}
//根据a[n]计算最早出现的位置
ll gps(ll an){
    if(an==1) return 1;
    an--; //同上
    ll pos=0;
    for(int i=62;i>=0;i--){
        while(p[i]<=an){
            an-=p[i];
            pos+=cnt[i];
        }
    }
    return pos+1;
}
int main() {
#ifdef LOCAL
    freopen("in.txt","r",stdin);
#endif // LOCAL
    int T;
    ll n;
    scanf("%d",&T);
    init();
    ll _inv = 500000004;//2的逆元
    while(T--){
        scanf("%lld",&n);
        ll an = caln(n);
        ll cnt = n - gps(an);//a[n]出现的实际次数
        ll ans = 0;
        for(int i=1;p[i-1]<=an;i++){//枚举次数,终结条件为某个等差数列的首项大于a[n]
            ll x1 = p[i-1]; //首项
            ll d = p[i]; //公差
            //项数。注意,正常的项数应该是((an-x1)/d+1),但这里不能保证a[n]全部出现了,
            //所以当((an-x1)%d==0)时说明a[n]位于当前的等差数列中,需要根据实际个数来计算,于是不+1
            ll num = ((an-x1)%d==0)?((an-x1)/d):((an-x1)/d+1); 
            ll xn = x1 + (num-1)*d; //尾项
            ll sum = (x1%mod+xn%mod)%mod*(num%mod)%mod*_inv%mod; //等差数列前num项和
            ans = (ans+i*sum%mod)%mod; //加入答案,共出现i次
            if((an-x1)%d==0)
                ans=(ans+cnt*(an%mod)%mod)%mod; //a[n]位于此数列,特别计算一下。
        }
        printf("%lld
",ans+1);//由于计算中忽略了第一项1,最后加上
    }
    return 0;
}
原文地址:https://www.cnblogs.com/fht-litost/p/9363629.html