AC 自动机可以看作是字典树 + KMP,其主要构建步骤为:
AC 自动机主要应用于多模式串匹配问题,即一个主串多个匹配的模式串问题。
AC 自动机基于字典树结构,将所有模式串插入字典树中,然后对字典树中的每个结点构造失配指针。AC 自动机中的失配指针与 KMP 中不同的是,AC 自动机中的失配指针是相对于整棵字典树的,即失配指针不再是局限于当前模式串,而是对于整棵字典树中所有的模式串而言的。
\begin{array}{c} fail[u] = trie[fail[p]][c] \end{array}
\begin{array}{c} fail[u] = trie[fail[p]][c] \end{array}
即当前结点的失配指针指向存在的最近的祖先结点的失配指针对应结点连接边字符 c 指向的结点。
#include <bits/stdc++.h>
using namespace std;
#ifndef _AUTOMATON_
#define _AUTOMATON_
#define ll int
#define MAXN 2000005
#define MAXCHAR 128
// AC 自动机
struct Automaton {
ll cnt; // 动态开点
ll trie[MAXN][MAXCHAR]; // 字典树
ll exist[MAXN]; // 字符串标记
ll fail[MAXN]; // 失配数组
Automaton(): cnt(0) {}
// 插入字符串(构建字典树)
void insert(char *str, ll n) {
ll p = 0;
for(ll i = 0; i < n; ++i) {
char ch = str[i];
if(!trie[p][ch]) {
trie[p][ch] = ++cnt;
}
p = trie[p][ch];
}
++exist[p];
}
// 构建 AC 自动机
void build() {
queue <ll> q;
// BFS 遍历字典树
for(ll i = 0; i < MAXCHAR; ++i) {
if(trie[0][i]) q.push(trie[0][i]);
}
while(q.size()) {
ll u = q.front();
q.pop();
for(ll i = 0; i < MAXCHAR; ++i) {
if(trie[u][i]) {
fail[trie[u][i]] = trie[fail[u]][i];
q.push(trie[u][i]);
} else {
// 路径压缩
trie[u][i] = trie[fail[u]][i];
}
}
}
}
// 多模式匹配
// 统计主串中出现的不同模式串数量
ll query(char *str, ll n) {
ll u = 0, res = 0;
for(ll i = 0; i < n; ++i) {
u = trie[u][str[i]];
// -1 标记优化(统计过一次后就不需再次统计)
for(ll j = u; j && ~exist[j]; j = fail[j]) {
res += exist[j];
exist[j] = -1;
}
}
return res;
}
};
#endif