感觉挺不错的题。
因为单词很少,于是可以状压。
dp[i][j][mask] 表示长度为i的字符串,走到AC自动机上第j个节点时,状态为mask的方案数。
mask为二进制,代表选了哪些单词。
转移时我们将状态转移给下一个AC自动机节点,设该节点为u,取了编号为k的单词,那么:
dp[i + 1][u][mask | k] += dp[i][j][k]
将每个mask选择了的字符串的个数预处理出来,统计答案时只需要将个数不小于k的贡献加上。
#include <iostream>
#include <cstdio>
#include <cstring>
#define cls(a, x) memset(a, x, sizeof(a))
using namespace std;
const int maxn = 30, maxnode = 105, maxd = 1100, maxq = 10000, p = 20090717;
int n, m, k, dp[maxn][maxnode][maxd], q[maxq], cnt[maxd];
struct _acm {
int son[maxnode][26], fail[maxnode], acmcnt, w[maxnode];
void init() {
cls(son, 0); for(int i = 0; i < maxnode; i++) fail[i] = w[i] = 0; acmcnt = 0;
}
void insert(const string &s, int c) {
int now = 0, len = s.size();
for(int i = 0; i < len; i++) {
int &pos = son[now][s[i] - 'a'];
if(!pos) pos = ++acmcnt;
now = pos;
}
w[now] = 1 << c;
}
void getfail() {
int h = 0, t = 0;
for(int i = 0; i < 26; i++) if(son[0][i]) q[t++] = son[0][i];
while(h != t) {
int u = q[h++];
for(int i = 0; i < 26; i++) {
if(!son[u][i]) son[u][i] = son[fail[u]][i];
else fail[q[t++] = son[u][i]] = son[fail[u]][i];
w[son[u][i]] |= w[son[fail[u]][i]];
}
}
}
} acm;
int main() {
ios::sync_with_stdio(false);
for(int i = 0; i < 1024; i++) cnt[i] = __builtin_popcount(i);
while(1) {
cin >> n >> m >> k;
if(!n && !m && !k) break;
acm.init();
for(int i = 0; i < m; i++) {
string str; cin >> str;
acm.insert(str, i);
}
acm.getfail();
cls(dp, 0);
int M = 1 << m;
dp[0][0][0] = 1;
for(int i = 0; i <= n; i++) for(int j = 0; j <= acm.acmcnt; j++) for(int k = 0; k < M; k++) {
if(!dp[i][j][k]) continue;
for(int t = 0; t < 26; t++) {
int u = acm.son[j][t];
dp[i + 1][u][acm.w[u] | k] += dp[i][j][k];
dp[i + 1][u][acm.w[u] | k] %= p;
}
}
int ans = 0;
for(int i = 0; i <= acm.acmcnt; i++) for(int j = 0; j < M; j++) if(cnt[j] >= k)
ans += dp[n][i][j], ans %= p;
printf("%d\n", ans);
}
return 0;
}