分析
可以設狀態 \(f_{l,r,k}\) 表示區間 \([l,r],bit(x\in[l,r])\le k\)的{字首長度,字尾長度,總方案數}。
合併即找一個 \(mid\),類似最大子段和的合併。
如何找個 \(mid\) 是解題的關鍵,關於二進位制分治題目,令 \(mid\) 為 highbit 或
lowbit 通常有很好的性質,本題 \(mid\) 為highbit。
設 \(mid=2^m-1\),其中 \(2^m\) 是 \(n-1\) 的 highbit,轉移方程有:
\(f_{0,n-1,k}=merge(f_{0,2^m-1,k},merge(2^m,n-1,k))\)
觀察後面狀態,發現highbit都為 2^m,考慮去掉這一位 1,轉移方程變成:
\(f_{0,n-1,k}=merge(f_{0,2^m-1,k},merge(0,n-1-2^m,k-1))\)
發現 \(l\) 恆等於 0,於是將區間改為長度,即 \(f_{i,k}\) 表示(從0開始)長度為 \(i,bit(x) \le k\) 的{字首長度,字尾長度,總方案數}。
轉移方程改為 \(f_{n,k}=merge(f_{2^m,k},f_{n-2^m,k-1}\),但此時時間複雜度依然是線性的。
發現轉移前部分狀態是固定,考慮預處理,設 \(g_{k,i}\) 為 \(bit(x)\le k\),長度為 \(2^i\) 的狀態。
轉移方程類比上式有:
\(g_{k,i}=merge(g_{k,i-1},g_{k-1,i-1})\)
於是,時間複雜度變為 \(O(klogn+logn)\),可以透過此題。
程式碼
int n,k;
int mul(int x,int y){
int res=0;
while(y){
if(y&1) res=(res+x)%mod;
x=(x+x)%mod;y>>=1;
}
return res;
}
struct state{
int len,lmx,rmx,ans;
friend state operator +(state a,state b){
state c;
c.lmx=a.lmx;
if(a.lmx==a.len) c.lmx=max(c.lmx,a.len+b.lmx);
c.rmx=b.rmx;
if(b.rmx==b.len) c.rmx=max(c.rmx,b.len+a.rmx);
c.ans=(a.ans+b.ans+mul(a.rmx,b.lmx))%mod;
c.len=a.len+b.len;
return c;
}
}dp[K][K];
state solve(int n,int k){
if(n==0) return {0,0,0,0};
if(k==0) return {n,1,(n==1),1};
int t;
for(t=60;~t;t--) if(n>>t&1ll){break;}
return dp[k][t]+solve(n-(1ll<<t),k-1);
}
void Main(){
n=rd,k=rd;
cout<<solve(n,k).ans<<endl;
}
signed main(){
for(int i=0;i<=60;i++)
dp[0][i]={1,1,(i==0),1};
for(int k=1;k<=60;k++){
dp[k][0]={1,1,1,1};
for(int i=1;i<=60;i++){
dp[k][i]=dp[k][i-1]+dp[k-1][i-1];
}
}
int T=rd;
while(T--) Main();
}