[JSOI2007]文本生成器
题目链接:luogu P4052
题目大意
一个东西会生成长度为 m 的由大写字符组成的字符串,然后问你生成的所有字符串中,有多少个字符串是可读的。
可读的仅当至少某些规定的字符串的其中一个在这个字符串出现过。
思路
这道题,看到要跟很多个字符串匹配,自然想到用 AC 自动机。
但是用脚想一想都知道不能直接暴力一个一个看。
那怎么办呢?
求出现过很麻烦,那我们不如找不可读的数量,然后把全部减去不可读的数量。
那怎么求不可读的呢?
就是构建出来的字符串里面不能出现给定的字符串。
那我们就在 Trie 树里面搞一个 bool 值记录这个字符串是不是可读的。
(也就是说,如果是真,就是可读的,我们算不可读的数量的时候就不能选它)
那怎么求出整个 Trie 树里面的这个 bool 值呢?
那我们可以想到,如果一个位置跳 fail 边,跳到的位置的这个 bool 值是真,那这个位置也应该是真。
那我们就可以在构建 fail 边的时候把这个值求出来。
接着,我们考虑用 dp 求不可读的字符串数量。
设
f
i
,
j
f_{i,j}
fi,j 表示当前
i
i
i 位的字符串,然后匹配到 Trie 树上的节点
j
j
j 时,不可读的字符串数量是多少。
那我们就枚举当前长度当前点的二十六个儿子,如果儿子的 bool 值不是真,就让长度加一位置是这个点的这个儿子的 dp 值加上你这个位置的 dp 值。
说的很乱,其实就是
f
i
+
1
,
s
o
n
j
,
k
=
f
i
+
1
,
s
o
n
j
,
k
+
f
i
,
j
f_{i+1,son_{j,k}}=f_{i+1,son_{j,k}}+f_{i,j}
fi+1,sonj,k=fi+1,sonj,k+fi,j。(前提是
c
a
n
t
s
o
n
j
,
k
=
=
0
cant_{son_{j,k}} == 0
cantsonj,k==0)
然后就枚举长度,节点,以及哪个儿子,dp 一下。
最后我们只要用全部字符串的数量减去 ∑ i = 0 K K d p m , i \sum\limits^{KK}_{i=0}dp_{m,i} i=0∑KKdpm,i ( K K KK KK 是 Trie 树的节点数,其实就是减去最后每一种结束情况的字符串数量),就是答案了。
至于全部字符串怎么求,就直接 2 6 m 26^m 26m 啊。
代码
#include<queue>
#include<cstdio>
#include<cstring>
#define mo 10007
using namespace std;
struct Trie {
int son[31], fail, num;
bool cant;
}tree[6001];
int n, m, size[61], thi, now, dp[101][6001], ans, KK;
char c[61][101];
queue <int> q;
void build_Trie(int thistime) {
now = 0;
for (int i = 0; i < size[thistime]; i++) {
thi = c[thistime][i] - 'A';
if (!tree[now].son[thi]) tree[now].son[thi] = ++KK;
now = tree[now].son[thi];
}
tree[now].cant = 1;
}
void get_fail() {
for (int i = 0; i < 26; i++)
if (tree[0].son[i]) {
tree[tree[0].son[i]].fail = 0;
q.push(tree[0].son[i]);
}
while (!q.empty()) {
now = q.front();
q.pop();
for (int i = 0; i < 26; i++) {
if (tree[now].son[i]) {
tree[tree[now].son[i]].fail = tree[tree[now].fail].son[i];
q.push(tree[now].son[i]);
tree[tree[now].son[i]].cant |= tree[tree[tree[now].son[i]].fail].cant;
//如果它fail边连着的点已经是不行,那这个点也不行
}
else tree[now].son[i] = tree[tree[now].fail].son[i];
}
}
}
void ksm(int x, int y) {
ans = 1;
while (y) {
if (y & 1) ans = (ans * x) % mo;
x = (x * x) % mo;
y >>= 1;
}
}
int main() {
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++) {
scanf("%s", &c[i]);
size[i] = strlen(c[i]);
build_Trie(i);
}
get_fail();
dp[0][0] = 1;//dp求出方案
for (int i = 0; i < m; i++) {
for (int j = 0; j <= KK; j++)
for (int k = 0; k < 26; k++)
if (!tree[tree[j].son[k]].cant)
dp[i + 1][tree[j].son[k]] = (dp[i + 1][tree[j].son[k]] + dp[i][j]) % mo;
}
ksm(26, m);
for (int i = 0; i <= KK; i++)
ans = ((ans - dp[m][i]) % mo + mo) % mo;
printf("%d", ans);
return 0;
}