去年省赛题现在才AC。
我早应该补的,这道题我学到了好多知识点,值了。
省赛的时候看了两个小时没看出来是dp,就算看出来了也ac不了,太菜了。。
题意:
给你n个开关,一共是kk轮,一轮可以关m个灯(这m个灯必须都不同),问你有多少种方法能从初始状态到达最终状态。
分析:
- dp[i][j]表示到第i轮的时候,有j个灯是合法的情况数。
- 转移其实不难想,主要是枚举的对象,如果枚举的是第i轮的合法数,则子问题不好确定,if判断条件也不好写(至少我没想出来。。);相反,如果枚举的是i-1轮的合法数,那么不但p的范围好确定,p确定之后该子问题的转移方向也就很清晰了(该状态可以向多个方向转移)
- 逆元的表要先打出来,不然会tle,因为每次求逆元会把复杂度提高一个数量级(顺带复习了一波大组合数求法)
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
//typedef __int128 lll;
#define print(i) cout << "debug: " << i << endl
#define close() ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
#define mem(a, b) memset(a, b, sizeof(a))
const ll mod = 998244353;
const int maxn = 1e2 + 10;
const int inf = 0x3f3f3f3f;
ll fac[maxn];
ll inv[maxn];
ll dp[maxn][maxn];
int v1[maxn], v2[maxn];
ll powmod(ll a, ll n)
{
ll res = 1;
while(n)
{
if(n & 1) res = res * a % mod;
n >>= 1;
a = a * a % mod;
}
return res;
}
void init()
{
fac[0] = 1;
for(int i = 1; i < maxn; i++) fac[i] = fac[i - 1] * i % mod, inv[i] = powmod(fac[i], mod - 2);
}
ll c(ll n, ll m)
{
if(m == 0 || m == n) return 1;
return fac[n] * inv[m] % mod * inv[n - m] % mod;
}
int main()
{
int t; cin >> t;
init();
while(t--)
{
mem(dp, 0);
int n, k, m; scanf("%d%d%d", &n, &k, &m);
int cnt = 0;
for(int i = 1; i <= n; i++) scanf("%1d", v1 + i);
for(int i = 1; i <= n; i++) scanf("%1d", v2 + i), cnt += v1[i] == v2[i];
dp[0][cnt] = 1;
for(int i = 1; i <= k; i++)
for(int j = 0; j <= n; j++)
for(int p = 0; p <= m; p++)
{
if(p <= j && m - p <= n - j)
{
dp[i][j - p + m - p] += dp[i - 1][j] * c(j, p) % mod * c(n - j, m - p) % mod;
dp[i][j - p + m - p] %= mod;
}
}
printf("%lld\n", dp[k][n]);
}
}