• 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;
    }
  • 相关阅读:
    Java后端工程师的学习技术栈
    ltp 分析 fail testcase
    程序员这个职业需要具备的素养
    你真的愿意到了50岁还要做编程吗?
    程序员的学习和积累
    程序员写博客的缘由
    VS2010生成文件
    从菜鸟到专家的五步编程语言学习法
    程序设计的18大原则
    怎样的代码才算是干净的代码?
  • 原文地址:https://www.cnblogs.com/fht-litost/p/9363629.html
Copyright © 2020-2023  润新知