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;
}