CF1997F Chips on a Line 题解

注意到操作是可逆的,可以先把所有筹码移动到位置 \(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;
}
posted @ 2024-08-01 18:41  zifanwang  阅读(22)  评论(0编辑  收藏  举报