6304 Chiaki Sequence Revisited[2018杭電多校聯賽第一場 G](找規律+位運算+逆元)
阿新 • • 發佈:2019-02-07
【題意】
給定一個序列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;
}