[复习] AC自动机
[复习] AC自动机
自动机
从一个状态通过接收一个信号转移到另一个状态。
其实就是从一个点走一种颜色的边到达另一个点,你会有一个初始点,然后每次走当前要走的颜色的边,会走到一个目标点,目标点保存着需要的答案。
AC自动机
以 \(Trie\) 为基础,\(kmp\) 的前缀函数思想构建的自动机。
用于解决多模式串匹配等任务。
例题:
给你一个文本串 \(S\),和 \(n\) 个模式串 \(T_i\),求每个模式串在文本串中出现了几次。
\(|S|\le 2*10^5,n\le2*10^5,\sum T_i\le2*10^5\)。
我们不能做 \(n\) 次 \(kmp\),所以把它“合成一次做”。
考虑把每个模式串插入 \(Tire\) 上。
每个节点是一个前缀,对于每个节点 \(S\) 维护这个 \(Trie\) 上最长的 \(S\) 的真后缀,即 \(border\),在这里我们称为失配指针 \(fail\)。
计 \(tr[u][i]\) 表示 \(Trie\) 树的树边,\(fail[u]\) 为失配指针。
-
对于 \(u\) 没有的边 \(i\),我们给它重新连向 \(tr[fail[u]][i]\)。
-
而如果有这条出边,那么得到 \(tr[u][i]\) 的 \(fail\) 为 \(tr[fail[u]][i]\)。
我们在 \(trie\) 上 \(bfs\) 从而得到这两个数组,当原本就有 \(tr[u][i]\) 时,将 \(trie[u][i]\) 加入队列。
for(int i=0;i<26;i++){
if(trans[0][i])q.push(trans[0][i]);
}
while(!q.empty()){
int u=q.front();q.pop();
for(int i=0;i<26;i++){
if(trans[u][i]){
fail[trans[u][i]]=trans[fail[u]][i];
q.push(trans[u][i]);
}
else trans[u][i]=trans[fail[u]][i];
}
}
这里有个细节就是,第一个字符的 \(fail\) 一定为 \(0\),所以要先遍历根的儿子(就像前缀函数要从 \(2\) 开始跑一样)。
我们把 \(Trie\) 上没有的边都加上了,那现在 \(tr\) 数组就是一个转移函数,我们遍历文本串,每新加一个字符就走一条边,如此一来到达的节点就是当前文本串的前缀最长的在 \(Trie\) 上的后缀。
我们从一个点一直跳 \(fail\),如果跳到了一个模式串的末尾,那么说明这个模式串是当前文本串前缀的后缀,即是文本串的子串。
我们不用每次跳 \(fail\),这样时间复杂度是不正确的。
注意到 \(fail\) 是一棵树,我们可以每次在当前点打上标记,到最后再遍历树统计。