题意
给你一排 (n) 个灯泡,让你进行 (k) 轮操作,每轮操作可以点亮或熄灭 (m) 个不同的灯泡。然后给你这排灯泡的初态和末态,问你在 (k) 轮操作后能让这排灯泡到达末态的方案有多少种(对 (998244353) 取模)。
题解
首先可以发现,终状态与起始状态不同的开关一定被改变了奇数次,反之偶数次。设 ( ext{dp}[i][j]) 表示第 (i) 轮有 (j) 个位置是被改变了奇数次。那么可以发现,假设这次有 (j) 个位置是奇数次,这次有 (k) 个位置从奇数次变成了偶数次,那么很明显,剩下 (m-k) 个位置就是从偶数次变成了奇数次。那么 ( ext{dp}[i+1][j-k+m-k]+= ext{dp}[i][j]cdot C_{j}^{k}cdot C_{n-j}^{m-k}) 这个式子就出来了,也就是下一轮的 (j-k+m-k) (之前有 (j) 个奇数次位,减去了这次变成偶数位的个数 (k),再加上这次由偶数位变成奇数位的个数 (m-k)),这也就是 ( ext{dp}[i][j]) 中的 (j) 个奇数位中选择 (k) 个,从剩下的 (n-j) 个偶数位中选出 (m-k) 个,得到的就是如上的式子。
这个问题的第二个关键点在于初值的赋值。有两种显而易见的赋值方式,( ext{odd_num}) 指的是两个串中状态不同的位置的个数,一种是开头 ( ext{dp}[0][0]),结束 ( ext{dp}[k][ ext{odd_num}]),也就是刚开始没有奇数位变化的位,最后出现有奇数位变化,还有一种是 ( ext{dp}[0][ ext{odd_num}],\, ext{dp}[n][0]),可以发现只有第二种是正确的,因为第一种到最后只能保证有 ( ext{odd_num}) 个位发生变化,不能保证是哪几个,而如果第二种,就先保证了起始有 (m) 个位需要改变只有一种情况。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define per(i,a,b) for(int i=a;i>=b;i--)
const int MAXN = 1e2+5;
const int MOD = 998244353;
ll dp[MAXN][MAXN];///dp[i][j]表示第i轮有j位是奇数的情况
ll c[MAXN][MAXN];///组合数
char s1[MAXN],s2[MAXN];
int main(){
rep(i,0,100)c[i][0]=1;
rep(i,1,100)c[i][i]=1;
rep(i,2,100){
rep(j,1,i-1){
c[i][j]=(c[i-1][j-1]+c[i-1][j])%MOD;
}
}
int t,n,k,m;
scanf("%d",&t);
while(t--){
scanf("%d%d%d%s%s",&n,&k,&m,s1,s2);
int num=0;
rep(i,0,n-1){
if(s1[i]!=s2[i])num++;
}
memset(dp,0,sizeof(dp));
dp[0][num]=1;
rep(i,0,k-1){
rep(j,0,n){///之前有j个奇数次变化的位置
rep(k,0,m){///有k个奇数变成了偶数
if(j>=k&&n-j>=m-k){
dp[i+1][j-k+m-k]+=dp[i][j]*c[j][k]%MOD*c[n-j][m-k]%MOD;
dp[i+1][j-k+m-k]%=MOD;
}
}
}
}
printf("%lld
",dp[k][0]);
}
return 0;
}