orz AC自动机。
Address
https://www.lydsy.com/JudgeOnline/problem.php?id=1030
Solution
AC 自动机上 DP 经典题。
首先断定,「至少一个」是不好做的。
于是转化一下,用
26m
26
m
减去「一个串都没有」。
把所有的单词串建成 AC 自动机。
然后就能设计出一个最经典的 AC 自动机上 DP 模型了!
f[i][u]
f
[
i
]
[
u
]
表示前
i
i
个字符,走到 AC 自动机上的节点 的串数。
为了方便一些萌新,这里讲一下「走到节点
u
u
」的具体含义:
将前 个字符组成的串在 AC 自动机上运行(按顺序对于每个字符,从根节点开始,对于每个字符,往对应的字符边走,这样一共走
i
i
步),最后到达的节点为 。
当然,为了确保「一个串都没有」,这里的
u
u
必须满足:
节点 表示的字符串的任意一个前缀都不等于任意一个单词串。
这等价于
u
u
在 Trie 上的祖先(包括自己)中,任意一个点都没有被标记为单词串。可以用 AC 自动机的 fail 来判断。
转移也就是枚举第 个字符
c
c
:
这样,不包含任意单词串的文章总数为:
用 26m 26 m 减去上式,就得到最终需要输出的答案。
Code
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
using namespace std;
const int N = 105, E = 1e4 + 5, PYZ = 10007;
int n, m, QAQ, que[E], len, f[N][E]; char s[N]; bool orz[E];
struct cyx {
int go[26], cnt, fail; void init() {memset(go, 0, sizeof(go)); cnt = fail = 0;}
} T[E];
void ins() {
int i, len = strlen(s + 1), u = 1; For (i, 1, len) {
int c = s[i] - 'A'; if (!T[u].go[c]) T[T[u].go[c] = ++QAQ].init();
u = T[u].go[c];
}
T[u].cnt++;
}
void cyx_dalao() {
int i, c; T[0].init(); For (i, 0, 25) T[0].go[i] = 1;
T[que[len = 1] = 1].fail = 0; For (i, 1, len) {
int u = que[i]; For (c, 0, 25)
if (!T[u].go[c]) T[u].go[c] = T[T[u].fail].go[c];
else {
int v = T[u].go[c], w = T[u].fail;
while (!T[w].go[c]) w = T[w].fail;
T[v].fail = T[w].go[c]; que[++len] = v;
orz[v] = orz[T[v].fail] || T[v].cnt;
}
}
}
int DP() {
int i, j, c, ans = 0, sum = 1; For (i, 1, m) sum = sum * 26 % PYZ;
f[0][1] = 1; For (i, 0, m - 1) For (j, 1, QAQ) if (!orz[j])
For (c, 0, 25) if (!orz[T[j].go[c]]) {
int x = T[j].go[c]; f[i + 1][x] = (f[i + 1][x] + f[i][j]) % PYZ;
}
For (j, 1, QAQ) ans = (ans + f[m][j]) % PYZ; return (sum - ans + PYZ) % PYZ;
}
int main() {
int i; cin >> n >> m; T[QAQ = 1].init();
For (i, 1, n) scanf("%s", s + 1), ins(); cyx_dalao(); cout << DP() << endl;
}