- AC自动机可以处理多模式串匹配问题。
问:事先给定若干个模式串(短一点的),多次询问一个文本串被多少个模式串匹配。
解:
AC自动机的建立过程:
①:把这若干个模式串插入到
t
r
i
e
trie
trie树里面。
②:第二层点的
f
a
i
l
fail
fail指针指向根
r
t
rt
rt。再把第二层的点放进队列。
③:按照
b
f
s
bfs
bfs的顺序。拿出队列头的节点
x
x
x,把它存在的儿子的
f
a
i
l
fail
fail指针设置为
x
x
x的
f
a
i
l
fail
fail指针的那个儿子。把它不存在的儿子也赋值为
x
x
x的
f
a
i
l
fail
fail指针的那个儿子。
处理询问的过程:
维护一个当前指针
c
u
r
cur
cur表示当前匹配到什么位置。枚举文本串的每一位,
c
u
r
cur
cur就在
A
C
AC
AC自动机上来回跳跃。一旦跳到一个点是原来串的末尾节点,就代表这个串被匹配上了。统计到答案里面。每次都不会重复。
这里要特别注意。如果一个位置被匹配上了,要一直向上根据fail跳到根。把这条路径(对应一个前缀)上的答案都统计上。因为AC自动机上的跳动可以跨越一些子树,不一定它的到根节点的路径上的答案都被统计过了。
#include <bits/stdc++.h>
using namespace std;
const int N = 2e4 + 5, M = 105, K = 1e6 + 5;
struct poi {
int ch[27], fail, cnt;
vector<int> id;
}tr[N];
char bc[166][M], s[M], t[K];
int n, rt, tot, res[M], ans[N], sum, pp;
inline void insert(char *s, int id) {
int len = strlen(s), cur = rt;
for (int i = 0; i < len; ++i) {
int &nxt = tr[cur].ch[s[i] - 'a'];
if (!nxt) nxt = ++tot;
cur = nxt;
}
tr[cur].id.push_back(id); tr[cur].cnt++;
}
queue<int> q;
inline void build_fail() {
for (int i = 0; i < 26; ++i) {
if (tr[rt].ch[i]) {
tr[tr[rt].ch[i]].fail = rt;
q.push(tr[rt].ch[i]);
}
}
while (!q.empty()) {
int x = q.front(); q.pop(); //printf("x = %d\n", x);
for (int i = 0; i < 26; ++i) {
if (tr[x].ch[i]) tr[tr[x].ch[i]].fail = tr[tr[x].fail].ch[i], q.push(tr[x].ch[i]);
else tr[x].ch[i] = tr[tr[x].fail].ch[i];
}
}
}
inline void clear() {
for (int i = 0; i <= tot; ++i) {
for (int j = 0; j < 26; ++j) tr[i].ch[j] = 0;
tr[i].id.clear();
tr[i].fail = tr[i].cnt = 0;
}
tot = 0;
}
int main() {
//freopen("data.in", "r", stdin);
//freopen("data.out", "w", stdout);
while (scanf("%d", &n) && n) {
clear();
for (int i = 1; i <= n; ++i) {
scanf("%s", bc[i]);
insert(bc[i], i);
}
//puts("qwq");
build_fail();
scanf("%s", t);
int len = strlen(t), cur = rt;
memset(ans, 0, sizeof ans);
for (int i = 0; i < len; ++i) {
cur = tr[cur].ch[t[i] - 'a'];
int p = cur;
while (p) {
int up = tr[p].id.size();
for (int j = 0; j < up; ++j)
ans[tr[p].id[j]] += tr[p].cnt;
p = tr[p].fail;
}
}
int p = 0;
for (int i = 1; i <= n; ++i) {
if (ans[i] > p) {
p = ans[i];
res[sum = 1] = i;
}
else if (ans[i] == p) res[++sum] = i;
}
printf("%d\n", p);
for (int i = 1; i <= sum; ++i) printf("%s\n", bc[res[i]]);
}
return 0;
}
注意:AC自动机上是会有自环的。
所以,我们可以将失配指针指向的的节点理解为:
当前节点所代表的串,最长的、能与后缀匹配的,在Trie中出现过的前缀所代表的节点。