1. 程式人生 > >6304 Chiaki Sequence Revisited[2018杭電多校聯賽第一場 G](找規律+位運算+逆元)

6304 Chiaki Sequence Revisited[2018杭電多校聯賽第一場 G](找規律+位運算+逆元)

【題意】
給定一個序列a,定義a[1]=a[2]=1,a[n]=a[n-a[n-1]]+a[n-1-a[n-2]](n>=3),求該序列的前n項和是多少,結果對 1e9+7 取模

【輸入格式】
第一行為資料組數T(T<1e5),下面T行每行一個整數n(n<1e18)

【輸出格式】
每組資料輸出一行,輸出每個n對應的前n項和

【思路】
先打個表,大佬們說規律就是序列中的每個數字i會出現log[lowbit(i)]+1次,其中lowbit(i)=i&-i,我表示根本看不出來。然後可以發現
[1,1]中的所有數字出現了1次 1=2^1-1
[1,2]中的所有數字出現了3次 3=2^2-1
[1.4]中的所有數字出現了7次 7=2^3-1
[1,8]中的所有數字出現了15次 15=2^4-1
….
不只是這樣,這些區間還可以相加,比如[1,1]和[1,4]合起來後[2,5]中的所有數字也是出現7次,然後就根據這樣一個規律去二分n,找出已經出現完的a[n],然後去計算所有[1,a[n]]的數字和,再加上剩下的幾項得到最終結果。計算和的時候又有一個等差數列的規律,比如算[1,15]的所有數字和,拆成若干個等差數列來計算
1 3 5 7 9 11 13 15 出現1次
2 6 10 14 出現2次
4 12 出現3次
8 出現4次
分別對每個等差數列求和,再乘以它出現的次數即可

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const ll mod=1e9+7;
const ll inv2=500000004; 

ll pw[80]; //pw[i]=2^i
ll cnt[80];//cnt[i]=2^(i+1)-1
ll n,pos;

void init(){//預處理 
    pw[0]=cnt[0]=1;
    for(int i=1;i<=62;++i){
        pw[i]=pw[i-1]*2;
        cnt[i]=2*pw[i]-1;
    }
}

ll getsum(ll p){//
計算序列中出現的所有1,2...p的和 ll ans=0; for(ll i=1;i<=p;i*=2){ ll num=(p-i)/(2*i); //首項=i, num=項數-1 ll last=i+num*(2*i);//公差是2*i, 算出末項 num=(num+1)%mod;//開始寫成++num然後就WA了,簡直要命 ll tmp=(i+last)%mod;//等差數列求和S=(首項+末項)*項數/2 tmp=tmp*num%mod; tmp=tmp*inv2%mod; tmp=tmp*(
__builtin_ffsll(i))%mod;//乘以對應的出現次數 //tmp=tmp*(__builtin_ctzll(i)+1)%mod;//等價寫法 ans=(ans+tmp)%mod; } return (1+ans)%mod;//加上第一項被忽略的1 } int main(){ init(); int T; scanf("%d",&T); while(T--){ scanf("%lld",&n); --n; if(0==n) { puts("1");continue; } pos=0; ll tmp=n; for(int i=62;i>=0;--i){//63的時候cnt會爆 if(tmp>=cnt[i]){ tmp-=cnt[i]; pos+=pw[i]; } } ll ans=getsum(pos); if(tmp) ans=(ans+tmp%mod*(pos+1)%mod)%mod;//如果有剩下的幾項,就再加上 printf("%lld\n",ans); } return 0; }