Description
JSOI 交给队员 ZYX 一个任务, 编制一个称之为 “文本生成器” 的电脑软件: 该软件的使用者是一些低幼人群, 他们现在使用的是 GW 文本生成器 v6 版. 该软件可以随机生成一些文章―――总是生成一篇长度固定且完全随机的文章----也就是说, 生成的文章中每个字节都是完全随机的.
如果一篇文章中至少包含使用者们了解的一个单词, 那么我们说这篇文章是可读的 (我们称文章 \(a\) 包含单词 \(b\), 当且仅当单词 \(b\) 是文章 \(a\) 的子串). 但是, 即使按照这样的标准, 使用者现在使用的 GW 文本生成器 v6 版所生成的文章也是几乎完全不可读的. ZYX 需要指出 GW 文本生成器 v6 生成的所有文本中可读文本的数量, 以便能够成功获得 v7 更新版. 你能帮助他吗?
Input
输入文件的第一行包含两个正整数, 分别是使用者了解的单词总数 \(n (n \leq 60)\), GW 文本生成器 v6 生成的文本固定长度 \(m\);
以下 \(n\) 行, 每一行包含一个使用者了解的单词. 这里所有单词及文本的长度不会超过\(100\), 并且只可能包含英文大写字母 \(A \ldots Z\).
Output
一个整数, 表示可能的文章总数. 只需要知道结果模 \(10007\) 的值.
Sample Input
2 2
A
B
Sample Output
100
Explanation
首先打算把 \(26^{100}\) 个字符串上去直接暴力试的都可以去一边了......
但是明显这是一道在 AC 自动机上动规的题目对不对......
考虑 \(dp[len][p]\) 为长度为 \(len\) 的字符串, 匹配到 AC 自动机上第 \(p\) 个节点的字符串的种类数, 则转移方程为:\[ dp[len][p] = \sum_{q = fail^{'}(p)}dp[len-1][q] \] 但是反过来推其实更显然一点...... 就是把节点的值转移到 \(fail\) 到的节点.
注意 \(flag\) 标记要沿着 \(fail\) 树的方向转移, 最后没有 \(flag\) 标记的节点一定是 无法被识别的, 然后去掉这些节点, 被所有可能产生的 \(26^m\) 个字符串减掉就可以了, 即:\[ ans = 26^m - \sum_{p \in nodes} dp[m][p]\ |\ flag(p) = true \] 那么这道题就做完了~
Source Code
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <queue>
#include <cstring>
using namespace std;
typedef long long lli;
const int maxn = 6010;
const int modr = 10007;
class AhoCorasickAutomaton
{
public:
int n, m, ncnt, root;
int ch[maxn][26], fail[maxn], flag[maxn];
void insert(char str[])
{
int p = root;
for (int i = 1; str[i] != '\0'; i++) {
int c = str[i] - 'A';
if (!ch[p][c])
ch[p][c] = ++ncnt;
p = ch[p][c];
}
flag[p] = true;
return ;
}
void build_tree(void)
{
queue<int> que;
que.push(root);
fail[root] = 0;
while (!que.empty()) {
int p = que.front();
que.pop();
for (int i = 0; i < 26; i++) {
if (!ch[p][i])
continue;
int q = fail[p];
while (!ch[q][i])
q = fail[q];
fail[ch[p][i]] = ch[q][i];
if (flag[ch[q][i]])
flag[ch[p][i]] = true;
que.push(ch[p][i]);
}
}
return ;
}
int dp[110][maxn];
void work_dp(int x)
{
for (int p = 1; p <= ncnt; p++) {
if (flag[p] || !dp[x-1][p])
continue;
for (int j = 0; j < 26; j++) {
int q = p;
while (!ch[q][j])
q = fail[q];
dp[x][ch[q][j]] += dp[x-1][p];
dp[x][ch[q][j]] %= modr;
}
}
return ;
}
void init(void)
{
root = ++ncnt;
for (int i = 0; i < 26; i++)
ch[0][i] = root;
return ;
}
int eval(void)
{
build_tree();
dp[0][1] = 1;
for (int i = 1; i <= m; i++)
work_dp(i);
int res_1 = 0, res_2 = 1;
for (int i = 1; i <= m; i++)
res_2 = res_2 * 26 % modr;
for (int i = 1; i <= ncnt; i++)
if (!flag[i])
res_1 = (res_1 + dp[m][i]) % modr;
return (res_2 - res_1 + modr) % modr;
}
} acm;
int n, m;
char str[maxn];
int main(int argc, char** argv)
{
scanf("%d%d", &n, &m);
acm.n = n;
acm.m = m;
acm.init();
for (int i = 1; i <= n; i++) {
scanf("%s", str + 1);
acm.insert(str);
}
int res = acm.eval();
printf("%d\n", res);
return 0;
}