CF1997F Chips on a Line 題解

zifanwang發表於2024-08-01

注意到操作是可逆的,可以先把所有籌碼移動到位置 \(1\),再進行若干次操作使籌碼數量最小化。

那麼我們只需要對每一個 \(i\) 知道有多少種情況把籌碼全移動到位置 \(1\) 後恰好有 \(i\) 個籌碼,和這類情況的最少籌碼數。

\(f_i\) 表示斐波那契數列的第 \(i\) 項,顯然一個位置 \(i\) 的籌碼移動到位置 \(1\) 後會變成 \(f_i\) 個籌碼。於是可以做一個 dp,記 \(\operatorname{dp}_{i,j,k}\) 表示前 \(i\) 個位置放 \(j\) 個籌碼,全部移動到位置 \(1\) 後有 \(k\) 個籌碼的方案數。需要滾動陣列,每次直接 \(\mathcal O(1)\) 轉移。

接下來考慮怎麼算最少籌碼數。因為操作是可逆的,所以每次可以將 \(f_i\) 個位置 \(1\) 的籌碼替換成一個位置 \(i\) 的籌碼。考慮貪心,從一個較大的 \(f_i\) 開始列舉,每次選擇儘量多的位置 \(1\) 的籌碼替換該位置的籌碼,顯然是最優的。

最後只需要把最少籌碼數等於 \(m\) 的方案數加起來就做完了,時間複雜度 \(\mathcal O(xf_xn^2)\)

參考程式碼:

#include<bits/stdc++.h>
#define mxn 200003
#define md 998244353
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define rept(i,a,b) for(int i=a;i<b;++i)
#define drep(i,a,b) for(int i=a;i>=b;--i)
using namespace std;
int n,x,m,ans,f[30],dp[2][1002][55002];
bool fl;
inline void add(int &x,int y){
	x+=y;if(x>=md)x-=md;
}
signed main(){
	cin>>n>>x>>m;
	f[1]=f[2]=1;
	rep(i,3,25)f[i]=f[i-1]+f[i-2];
	dp[0][0][0]=1;
	rep(i,1,x){
		fl^=1;
		rep(j,0,n)rep(k,0,f[i]*j){
			dp[fl][j][k]=dp[fl^1][j][k];
			if(j&&k>=f[i])add(dp[fl][j][k],dp[fl][j-1][k-f[i]]);
		}
	}
	rep(i,0,n*f[x]){
		int s=0,x=i;
		drep(j,25,2){
			s+=x/f[j];
			x%=f[j];
		}
		if(s==m)add(ans,dp[fl][n][i]);
	}
	cout<<ans;
	return 0;
}

相關文章