题意:有m个模式串,问长度为n的目标串中至少出现k种模式串的种类数,可以重叠且所有的串都只包含小写字母。
题解:因为模式串最多10个,可以用状态压缩来表示每一个模式串是否出现,f[i][j][k]是长度为i的字符串,结尾节点是j且当前所用模式串状态是k的所有可能情况。状态转移方程f[i][next[j][l]][k | val[next[j][l]] = sum{ f[i - 1][j][k] },val存的是到当前节点所得到的模式串状态。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#define ll long long
using namespace std;
const int N = 105;
const int SIGMA_SIZE = 26;
const int MOD = 20090717;
int Next[N][SIGMA_SIZE], fail[N], val[N], sz, n, m, p;
ll f[30][N][1 << 10];
int num[1050];
char str[N];
void init() {
memset(Next[0], 0, sizeof(Next[0]));
val[0] = 0;
sz = 1;
}
void insert(char *s, int v) {
int u = 0, len = strlen(s);
for (int i = 0; i < len; i++) {
int k = s[i] - 'a';
if (!Next[u][k]) {
memset(Next[sz], 0, sizeof(Next[sz]));
val[sz] = 0;
Next[u][k] = sz++;
}
u = Next[u][k];
}
val[u] |= (1 << v);
}
void getFail() {
queue<int> Q;
fail[0] = 0;
for (int i = 0; i < SIGMA_SIZE; i++)
if (Next[0][i]) {
fail[Next[0][i]] = 0;
Q.push(Next[0][i]);
}
while (!Q.empty()) {
int u = Q.front();
Q.pop();
val[u] |= val[fail[u]];
for (int i = 0; i < SIGMA_SIZE; i++) {
if (!Next[u][i])
Next[u][i] = Next[fail[u]][i];
else {
fail[Next[u][i]] = Next[fail[u]][i];
Q.push(Next[u][i]);
}
}
}
}
int main() {
for (int i = 0; i < 1024; i++) {
num[i] = 0;
for (int j = 0; j < 10; j++)
if (i & (1 << j))
num[i]++;
}
while (scanf("%d%d%d", &n, &m, &p) == 3 && n + m + p) {
init();
for (int i = 0; i < m; i++) {
scanf("%s", str);
insert(str, i);
}
getFail();
memset(f, 0, sizeof(f));
f[0][0][0] = 1;
int temp = 1 << m;
for (int i = 1; i <= n; i++)
for (int j = 0; j < sz; j++)
for (int k = 0; k < temp; k++) {
if (f[i - 1][j][k] > 0) {
for (int l = 0; l < SIGMA_SIZE; l++) {
int kk = k | val[Next[j][l]];
f[i][Next[j][l]][kk] = (f[i][Next[j][l]][kk] + f[i - 1][j][k]) % MOD;
}
}
}
ll res = 0;
for (int i = 0; i < temp; i++)
if (num[i] >= p) {
for (int j = 0; j < sz; j++)
res = (res + f[n][j][i]) % MOD;
}
printf("%lld\n", res);
}
return 0;
}