Aho-Corasick automaton,该算法在1975年产生于贝尔实验室,是著名的多模匹配算法。
前言
ZR的rxd老师加大力度讲了一发AC自动机,凭借着KMP自动机没有自闭的光环,我居然没 有 自 闭!
在晚上自习时重新看了一遍黄队的博客并做了几道题之后,我对AC自动机有了更深的了解,所以来这里瞎扯几句,以便之后复习.
参考资料
前置知识
Trie树(字典树
不知为啥rxd叫他字母树)KMP单字符串匹配算法
很多神仙都说过一个公式:
Aho-Corasick automaton = Trie + KMP
没错,AC自动机正是以Trie的结构为基础结合KMP的思想建立的。
建立
AC自动机是一个用来求解多模式串匹配问题的算法.
建立一个AC自动机通常需要两步:
- 一棵
Trie的结构,把所有的模式串插入Trie - KMP单字符串匹配算法的思想,对
Trie上的节点建立失配指针
Trie构建
和普通的Trie没有什么区别…,直接写一个Trie的Insert操作即可.
注意:这里Trie上的每一个节点是一个模式串的前缀.为了简便,我们将在下文把他称为一个状态(此处借用OI-wiki的约定),Trie的每一个节点表示一个状态,Trie的边可以看作状态的转移(这怎么有点像dp)
我们把所有这些状态组成的集合称为$U$
因为我们只利用字典树的结构,所以只用把模式串插入Trie即可.
构造失配指针
AC自动机利用一个失配指针failed来辅助匹配
状态(节点)u的failed指针指向状态v,且$v \in U$,并且v是u的最长后缀.
注意:这里的failed和KMP算法中的next指针的不同点在于next指针求的是最长的Border(也就是最长前后缀),而failed是最长后缀.
清楚不同点之后,我们利用KMP的思想来构建failed指针:
我们通过一遍BFS来构建,假设现在我们BFS到的节点为now,他的父亲为fa,fa通过字符str转移到now上,即Trie[fa][str] = now.
由于这是BFS,那么深度小于now的failed指针都已经被求出(这自然包括fa)
我们首先查询fa的failed指针:
如果
Trie[failed[fa]][str]指向的值w存在,那么我们就让failed[now] = w.如果上式并不存在,那么我们继续跳
failed,即找到failed[failed[p]],如果还是不存在,那么继续跳,直到找到满足的状态为止.如果跳到根节点也没有找到,那么直接令
failed[now]等于根节点

这里有一张来自OI-wiki的gif,帮助大家理解.
Trie图与AC自动机的优化
我们发现,在上述过程中的暴力跳failed这一段时间复杂度不太优秀,那么我们引入Trie图来优化.
我们考虑在当前节点的父亲的failed指针无法通过str字符进行转移的情况下,可以通过修改Trie树的结构来压缩状态.
我们考虑状态u,如果在u后面加上一个字符str向下转移成下一个状态v(我们记这个东西为u' = trans(u,str)).
如果
v存在,那么一定有一个模式串的前缀为v.如果
v不存在,那么我们让trans(u,str)指向trans(failed[u],str)对于上一个东西的解释:因为
failed[u]是状态u的后缀,那么显然trans(failed[u],str)就是u'的后缀
这里还是有一张来自OI-wiki的gif,帮助大家理解

可以发现,这些黑色的边将Trie树变成了Trie图,通过压缩状态简化了跳failed的时间
时间复杂度
AC 自动机的时间复杂度在需要找到所有匹配位置时是$O(|s|+m)$, $|s|$是文本串长度,$m$表示模板串的总匹配次数;
实现
我们以LuoguP3808为例.
1 | // luogu-judger-enable-o2 |