CodeForces - 1982E

SmileMask發表於2024-07-14

分析

可以設狀態 \(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();
}