原题链接
两个只包含0、1的字符串长度为n,有k次操作,每次操作选择m个数,将这m个数进行0 1反转(即0变为1,1变为0),问经过k次操作后,将第一个字符串变为第二个字符串共多少种方法,结果对998244353取余。
个人感觉DP最难的不是写转移方程,而是如何定义一个状态数组来表达结果
这个题就是用d[i][j]表示在第 i 次操作时与第二个字符串不同数为 j 时的方法总数
知道状态数组后然后在结合一下组合数学
是不是很容易就能写出转移方程捏
首先我们定义一个u表示要从上一次选择u个不同的数,v表示从上一次选择v个相同的数
那么这次计算的 j 来自上次的x
x=j+u-v
那么在满足x>=0&&x<=n&&x>=u&&(n-x)>=v的条件下
d[i][j]+= d[i-1][x] * C(x,u) * (n-x,v)
直接算会超时,优化一下,即将要用到的组合数打个表
代码:
#include <bits/stdc++.h>
#define ll long long
using namespace std;
ll d[105][105],c[105][105];
ll mod=998244353;
ll apow(ll a,ll b)
{
ll s=1;
while(b)
{
if(b&1)
{s=s*a%mod;}
a=a*a%mod;
b=b/2;
}
s%=mod;
return s;
}
ll C(ll n,ll k)
{
ll aa=1;
for(ll i=1;i<=n;i++)
{aa=aa*i%mod;}
ll bb=1;
for(ll i=1;i<=k;i++)
{bb=bb*i%mod;}
for(ll i=1;i<=n-k;i++)
{bb=bb*i%mod;}
return aa*apow(bb,mod-2)%mod;
}
void init()
{
for(int i=0;i<=102;i++)
{
for(int j=0;j<=i;j++)
{c[i][j]=C(i,j);}
}
}
int main()
{
init();
ll t,n,m,k;
char s[105],r[105];
scanf("%lld",&t);
while(t--)
{
memset(d,0,sizeof(d));
scanf("%lld %lld %lld",&n,&k,&m);
scanf("%s",s);
scanf("%s",r);
int cnt=0;
for(int i=0;i<n;i++)
{
if(s[i]!=r[i])
{cnt++;}
}
d[0][cnt]=1;
for(int i=1;i<=k;i++)
{
for(int j=0;j<=n;j++)
{
for(int u=m;u>=0;u--)
{
int v=m-u;
int x=j+u-v;
if(x>=0&&x<=n&&x>=u&&(n-x)>=v)
{
d[i][j]=(d[i][j]+(d[i-1][x]%mod)*(c[x][u]*c[n-x][v]%mod))%mod;
}
}
}
}
printf("%lld\n",d[k][0]);
}
return 0;
}