洛谷题单指南-状态压缩动态规划-P2167 [SDOI2009] Bill的挑战
原题链接:https://www.luogu.com.cn/problem/P2167
题意解读:求与n个模版字符串中的k个能匹配的字符串个数,匹配要求字符串长度相等,且对应位置的字符相同或者模版中是‘?’。
解题思路:
解题的关键在于要一位一位的看目标字符串能取的字符,取到某个字符之后,能与哪些模版字符串匹配。
我们可以定义状态g[i][j]表示当目标字符串第i位取字符j(a~z)的时候,有哪些模版字符串能与之匹配,二进制位为1的表示能匹配的模版字符串。
接下来,进行dp相关的状态设计:
设f[i][j]表示已匹配到字符串前i位(注意与g定义的不同),当前能与目标串匹配的模版字符串状态为j时的目标字符串的个数
那么,在状态转移时,当前已匹配到第i位,当前目标串状态为j,
接下来要考虑第i+1位能取什么字符,枚举c:'a'~'z',此时f[i][j]能给贡献的状态必然是 g[i][c] & j,
因此有f[i +1][g[i][c] & j] += f[i][j]
初始化:f[0][(1 << n) - 1] = 1
结果:所有状态s中1的个数为k的f[strlen][s]之和,注意计算中取模。
另外,在状态转移时,如果f[i][j] = 0,则可以剪枝,不必枚举c,因为贡献加上去没意义。
100分代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 15, M = 55, MOD = 1000003;
int g[M][26];
int f[M][1 << N];
string ss[N];
int t, n, m, k;
int count1(int x)
{
int res = 0;
while(x)
{
res++;
x -= (x & -x);
}
return res;
}
int main()
{
cin >> t;
while(t--)
{
cin >> n >> k;
memset(g, 0, sizeof(g));
memset(f, 0, sizeof(f));
for(int i = 0; i < n; i++) cin >> ss[i];
m = ss[0].size(); //字符串的长度
for(int i = 0; i < m; i++)
{
for(int j = 0; j < 26; j++)
{
for(int k = 0; k < n; k++)
{
if(ss[k][i] == '?' || ss[k][i] - 'a' == j)
{
g[i][j] |= (1 << k);
}
}
}
}
f[0][(1 << n) - 1] = 1;
for(int i = 0; i < m; i++)
{
for(int j = 0; j < 1 << n; j++)
{
if(!f[i][j]) continue;
for(int c = 0; c < 26; c++)
{
f[i + 1][g[i][c] & j] = (f[i + 1][g[i][c] & j] + f[i][j]) % MOD;
}
}
}
int ans = 0;
for(int i = 0; i < 1 << n; i++)
{
if(count1(i) == k)
{
ans = (ans + f[m][i]) % MOD;
}
}
cout << ans << endl;
}
return 0;
}
浙公网安备 33010602011771号