Appearance
即 AC 自动机。
ACAM 是 Kmp 和 Trie 的结合,用于解决一个在文本串匹配若干个模式串(字典)的问题。
简单来说就是在树上维护 Border。
Trie 上一个节点表示一个前缀,
求法也和单串的 Border 类似,
因为
时间复杂度分析同 KMP,指针的增量为 Trie 节点数。
时间复杂度:
匹配时,也和 Kmp 类似,在 Trie 上迭代
时间复杂度:
区别是,对于文本串
注:ACAM 因为要跳 Border 的特殊性,一般将 ACAM 中 Trie 的根设为
cpp
struct Trie {
/*
其它部分的 Trie 略
*/
void insert( int &p, string &s, int x) {
if (!p && x) // p = 0 为根
p = ++idx;
}
};
struct ACAM {
Trie trie;
int border[N];
void init() {
for (int i = 0; i <= trie.idx; i++)
border[i] = 0;
trie.init();
}
void build() {
queue<int> q;
for (int i = 0; i < 26; i++)
if (trie.son[0][i])
q.push(trie.son[0][i]);
while (q.size()) {
auto now = q.front();
q.pop();
for (int i = 0; i < 26; i++) {
if (trie.son[now][i]) {
int cur = border[now];
while (cur && !trie.son[cur][i]) {
cur = border[cur];
}
border[trie.son[now][i]] = trie.son[cur][i];
q.push(trie.son[now][i]);
}
}
}
}
void solve(string &s){
int now = 0;
for (auto u : s) {
while (now && !trie.son[now][u - 'a']) {
now = border[now];
}
now = trie.son[now][u - 'a'];
if (now)
vis[now]++;
}
}
};Trie 图优化
在 AC 自动机 Trie 上维护 Border 时,需要不断跳 Border 直到匹配成功,注意到一个点的 border 是唯一的,那么一个点匹配一个字符所跳 Border 的过程也是唯一的,那么可以将这一个过程进行路径压缩,压到
(其实严格意义上,只有补全了才能叫“自动机”)
若预处理
- 如果
的 后继边有节点,则 对应的后继节点。 - 否则,
。
在实际应用中,
此时,找到失配后匹配成功的最大 Border 只需要一次了。
但是因为
cpp
// 补全 Trie 图
for (int i = 0; i < 26; i++) {
auto &u = trie.son[now][i];
if (!u) {
u = trie.son[border[now]][i];
} else {
border[u] = trie.son[border[now]][i];
q.push(u);
}
}
// 匹配
int now = 0;
for(auto u : s){
now = trie.son[j][u - 'a'];
}