AC自动机学习笔记
前置知识
Trie树(字典树),KMP(思想)
AC自动机全名(这个不重要),是用于字符串匹配的玩意。
我们知道,在一个文本串里匹配模式串可以用KMP,但如果是匹配多个文本串呢?
假设有\(2\times 10^5\)个模式串,又该如何应对呢?这时就要用到AC自动机了。
先看一个题
文本串:
abcabbaab
模式串:
abba
ab
abc
bb
bc
很容易想到的做法是5次KMP,然后输出答案。但是数据大点你就直接炸了。
首先建一下Trie树

这里红色结点表示这个点为某一个串的末尾。
然后该怎么做?
先遍历,abc,成功匹配到一个模式串。接下来干啥?难道直接返回根节点重新匹配吗?这样根跑\(n\)次KMP没什么区别。
所以我们重头戏,失配指针\(fail\) TA来了。
我们看,abc,bc共有 bc这一部分。所以我能不能直接省去返回根节点,直接从3号节点跳到8号节点呢?
可以。但是得分情况。
如果他们只是有一部分重,比如ac和bac这样就不行。因为你不能保证跳完之后ac是否一定有一个b作为开头。
那是不是只要有开头重合...肯定不对。这很显然。
必须前一个串的后缀与后一个串的前缀重合,那我就能直接从后缀末尾跳到前缀的末尾。
那这个玩意怎么求?
\(fail\)指针一定比跳之前深度浅。
这很显然。
假设一个字符串长度为\(n\)
简单的证明:
后缀位置末尾:第\(n\)层
前缀长度\(\le n\)
前缀末尾\(\le\)第\(n\)层
最好的情况就是前缀长度=后缀长度=\(n\)那这俩串一定重合,所以不成立。
通过这个结论,我们可以推出第一层\(fail\)指针一定指向\(root\)(根节点)
我们还可以想到用bfs搜索。因为\(fail\)指针根深度会变得越来越浅,所以我们用bfs求到该层上一层已经全部求完了。
那我们是不是可以用KMP思想通过父节点的\(fail\)求出子结点的\(fail\)呢?
当然可以啦。
因为父亲节点的\(fail\)代表以父亲为末尾的后缀已经有一个前缀与他匹配。
接下来举个例子(只是思想,并非真正操作)
树和串根本不是一个东西啊喂(#`O′)
假设已经匹配的是这两个
X不是字符,只是表示上下不一样
XXXabcd
abcdeXX
那么abcd为公共的前后缀。
那么从前一个串的d可以跳到后一个串的d。
现在我的第一个串有个新来的儿子e,变成这样
XXXabcde
abcdeXX
(这里少一个)其实无所谓,反正要那部分重合的就行。
我们发现第二个串已经匹配的前缀正好也有个儿子e,那我肯定能调过来。
我第一个串又来了个儿子'f'变成
XXXabcdef
abcdeXX
先跳父亲e的\(fail\),来到第二个串
我们再看第二个串,咦?没有f?
那我只能再跳第二个串e的\(fail\),以此往复
那求出来\(fail\)又有啥用呢?
我们先求出来(想自己找的可以回去上面找一下\(fail指针试试\))

有点抽象是吧,那我简化一下。

现再当求了一个
字符串符合题意时,那TA的\(fail\)一定符合题意\(后缀嘛。是原来的一部分\)。然后继续匹配就能快乐的完成了。

浙公网安备 33010602011771号