ZJOI的SAM让我深受打击,WJZ大神怒D陈老师之T3是SAM裸题orz...我还怎么混?暂且写篇`从Trie谈到AC自动机`骗骗经验.
Trie是一种好玩的数据结构.它的每个结点存的是字母,因此得名`字母树`.
出一张图让大家感受下.
(image powered by SaiBu NaoCu)
上面那是一棵插入了
ape,app,applicant,application,bake,ban,banana
等词的Trie.红色结点表示接受态.
显然,查找时只需顺着链照下来,插入只需边查找边插入.
(删除只需除去接受态,或此时在它没有子结点时删除到它的最近接受态父结点)
好懂好写高效率.
AC自动机是一种基于Trie的数据结构.它是一个真正的自动机.
AC自动机,简单地说就是加了一些奇怪东西的Trie.
(powered by cocoa....Cacoo)
实线表示Trie上的路径,虚线表示Fail指针.
Fail指针是什么呢?当你在这个结点上时,对于下一个字符失匹配时你要走的路.很类似于KMP的next数组.
它的定义也是基本一样的.最长有相同前缀的后缀(的那个前缀的最后一个字母结点的指针).
使用起来更是一样.计算也是一样的.顺着fat(s[p-1])的fail(next[k])跳到可以匹配为止.
简单吧?和KMP很像.
那么问题又来了.怎么输出呢?
我们再画个图想想.
原来,顺着Fail指针一直走下去即可啊...
那么我们就有了用AC自动机匹配的算法.
匹配第一个`A`,从root往下找
找到匹配,A的匹配加一.向下寻找下一个字符找到Null.
因Fail指针回跳到root.寻找字符C.
寻找字符B.不是接受态,继续.
A是一个接受态.沿着Fail指针走回去输出结果.
-------------------
匹配C失跳.
------------------------------
...........................
...................最终
结果
那么,如何构造一个AC自动机呢?
显然可以在每加入一个结点时沿着它的父亲的Fail指针走,走到第一个有相同字符子结点的找到那个子结点,将Fail指向那个结点;如果到root还没找到有同字符子结点,将Fail指向root.
这是在线的做法.离线当然可以用BFS解决,这样保证了当一个结点被处理时它的父亲那层的结点已经处理完.Fail结点最近也只可能在父亲那层.复杂度应与在线办法相同.
Update: 犯了个SB错,AC自动机不可在线.原因Fail指针可能改变.
图表地址:https://cacoo.com/diagrams/xqj6UFk5zcllgHGW
原文:http://www.cnblogs.com/tmzbot/p/4418709.html