这道题之前YYR出过, 最近重做AC自动机于是又来旧题重做一下~
题目大意就是说给你m个字符串, 然后让你统计长度为n的并且由这m个字符串中的一些字符串紧凑拼成的字符串有多少个. 什么叫紧凑拼成? 可以理解为这个字符串包含这m个字符串中的一些, 相邻之间可以互相重叠但是不能有间隔——也就是说这个字符串的每一个字符一定要至少被给定的m个字符串之一覆盖.
这种计数方案数肯定要想到dp. 由于可以重叠很类似于跳fail所以我们又要想到AC自动机. 将m个字符串建成一个AC自动机, 设dp[i][j][k]表示当前长度为i的走到AC、自动机上的第j号节点当前末尾还有k个没有被覆盖. 那么采用刷表式(可以发现刷表式好写)dp, 枚举当前j的儿子, 如果j的儿子son是一个结尾节点的话, 那么就可以覆盖他最大覆盖长度cnt[son]这么多的字符. 那么当cnt[son] >= k + 1的时候, 就可以由dp[i][j][k]转移到dp[i + 1][son][0]. 为什么要 >= 呢? 因为当前到了son还剩k + 1个没被覆盖, 如果覆盖不到的话中间就有些没被覆盖, 就不符合题意了. 所以当 < k + 1的时候就转移到f[i + 1][son][k + 1].
注意cnt数组要从fail那里转移过来. 因为有些节点可能不是结尾节点, 但他的fail链上可能有个是, 那么根据fail定义那个结尾节点就是他的后缀, 那么他也包含了这个字符串.
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1005;
const int mod = 1e9 + 9;
char ss[11];
int n, m, tot, ans;
int mean[maxn], f[maxn][maxn][11];
int c[maxn][5], fail[maxn], cnt[maxn];
inline void insert() {
int p = 0;
int len = strlen(ss);
for (int i = 0; i < len; ++ i) {
int idx = mean[(int)ss[i]];
if (!c[p][idx]) c[p][idx] = ++ tot;
p = c[p][idx];
}
cnt[p] = len;
}
queue<int> q;
inline void bfs() {
for (int i = 0; i < 4; ++ i)
if (c[0][i]) q.push(c[0][i]);
while (!q.empty()) {
int u = q.front(); q.pop();
for (int i = 0; i < 4; ++ i) {
int v = c[u][i];
if (!v) {c[u][i] = c[fail[u]][i]; continue;}
fail[v] = c[fail[u]][i];
cnt[v] = max(cnt[v], cnt[fail[v]]);
q.push(v);
}
}
}
inline void add(int &a, int b) {
a += b;
a = (a >= mod) ? a - mod : a;
}
inline void dp() {
f[0][0][0] = 1;
for (int i = 0; i < n; ++ i)
for (int j = 0; j <= tot; ++ j)
for (int k = 0; k < 10; ++ k)
if (f[i][j][k])
for (int p = 0; p < 4; ++ p) {
int son = c[j][p];
if (cnt[son] >= k + 1) add(f[i + 1][son][0], f[i][j][k]);
else add(f[i + 1][son][k + 1], f[i][j][k]);
}
for (int i = 0; i <= tot; ++ i)
add(ans, f[n][i][0]);
}
int main() {
mean['A'] = 0, mean['G'] = 1, mean['C'] = 2, mean['T'] = 3;
scanf("%d%d", &n, &m);
for (int i = 0; i < m; ++ i) {
scanf("%s", ss);
insert();
}
bfs(), dp();
printf("%d\n", ans);
return 0;
}