AC自动机
序
在阅读本文前,请务必对 Trie
(字典树)有一定的了解。
自动机 ( ) ,相必各位刚来到洛谷时点开算法标签,就对在第一个板块中尤为显眼的它产生了兴趣。
而他的功能并不像名字那般玄幻, 只是用于实现字符串多对单匹配罢了。
何为多对单匹配?
给定一个字符串集合 , 还有一个文本串 , 试问有多少个 中的串在 中出现过?
你就会说了:“跑 次 KMP
” 不就好了?
真实的你:“KMP
是啥,好吃吗?”
不过,当你用 跑 KMP
时,若有 是 的子串,则跑 时就浪费了很多时间。
我们可以借鉴 Tarjan
求 LCA
的思想,既然对被询问的东西做不了手脚,那就对询问的东西做手脚。
“把 建成一颗 Trie
!”
在 1975 年想到了。
初始化
例子:
若文本串 。
字符串集 。
则初始的 如下图:
其中,蓝笔标的是 数组的值, 表示从根节点到 的这条路径形成的字符串(下文记作 )在 中是否出现。
朴素匹配的劣处与失配指针的引入
在某一位失配以后,朴素的想法是从开始匹配的位置下一位重新开始,但我们显然可以用一些思想来优化这个过程。
比如,我们匹配 时在 失配了,从 开始显然是浪费时间的。
那怎么办呢?
匹配既然要移动,那么当前匹配的字符串的一个前缀必定是失配子串的一个后缀,我们直接移动到最长公共前后缀处开始匹配即可。
由于 的性质,我们可以用 指针来记录这个过程。
失配指针的形式化定义
形式化地, 记录 在 中的最长后缀的位置。
例如 ,由于 出现过的最长后缀是 ,而 。
失配指针的构建
隆重地介绍两个由 lls 提出的法则:平行四边形法则与三角形法则。
平四法则
如果节点 沿着字符 来到了存在于 中一个点 ,则 为 沿着 走到的节点编号。
感性理解:由于 是 的后缀,显然 ,又因为 是最长后缀,所以 也是最长的。
画出来大概就是这个样子:
(红色的是失配指针,绿色的是原来的边和点)
三角法则
若 沿着 来到了一个不存在的点 ,那么如果匹配时也走了这条边,那么就失败了,所以 是一个失配点,直接把这条边连向失配指针就好了,这样查询的时候就可以无脑沿着失配指针跳了。
画出来大概就是这个样子:
(红色的是失配指针,绿色的是原来的边和点,蓝色的是新的边)
代码实现
容易发现,当前节点的构建依赖于它的前驱节点,所以用 实现。
查询
我们在自动机里不断跳来跳去,并且加上贡献,注意去重。
后记
感觉也不是那么难嘛,lls 牛逼!
贴一个我的板子吧: