zoukankan      html  css  js  c++  java
  • 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;
    }
  • 相关阅读:
    界面演示
    监控其它进程
    cmd 里面运行git提示“不是内部或外部命令,也不是可运行的程序”的解决办法...
    CI框架与Thinkphp框架的一些区别
    php 设置error_reporting(0)和ini_set('display_errors', 0)之后,还是显示错误
    PHPStorm2017去掉函数参数提示
    Cad 首尾相连的线段连接成多段线
    利用C#进行AutoCAD的二次开发 颜色和样式
    修改SDE最大连接数
    利用C#进行AutoCAD的二次开发(三)(转自明经通道)
  • 原文地址:https://www.cnblogs.com/fht-litost/p/9363629.html
Copyright © 2011-2022 走看看