- 直到现在才发现我对\(AC\)自动机还是一知半解(爆论
- 前期储备知识:
- 字典树 \(trie\)
- 单模式串匹配 \(KMP\)
- \(AC\)自动机看起来很高大上(仿佛考场上每道题写AC自动机都能过)。但其实上\(AC\)自动机不过是将\(KMP\)套在\(trie\)上(形象化来讲,即具体实现是利用这两种算法的思想,实际上从定义出发并不相同)
- 首先,当我们手头有\(N\)个模式串时,我们仿照\(KMP\)的思想,对于第\(x\)个模式串的任意一个位置\(pos\),找到最大的在这\(N\)个模式串的前缀\([start:]\)中出现过的后缀\([:pos]\),即\([start_s:]^{s}=[:pos]^{s_x}\)
- 但往下深入思考的时候,我们将遇到一个棘手的问题
- \[如果那个最长的前缀同时出现在多个模式串中怎么办?\\这样我们并不知道应该从哪个模式串的前缀末尾开始匹配\]
- 那怎么办呢?于是我们自然而然就能想到 用\(trie\)把这些相同的前缀合并起来,这样就解决了我们之前遇到的问题。
- 接下来,问题少年又开始提问题了。那你每次插入一个模式串都需要把前面的\(fail\)指针重建(因为会出现更长的前缀匹配前面的后缀),复杂度不是爆炸了吗?所以我们必须在所有模式串插入完成后在一起建\(fail\)指针,因此\(AC\)自动机从理论上讲是一个静态数据结构
- 既然\(fail\)指针的构建是同时对\(N\)个模式串,因此构建方式与\(KMP\)的\(fail\)指针有些许不同,在这里简略讲讲...
- 我们先在脑中构建一张包含你喜欢的字符串的\(trie\)树(建议拿张草稿纸画画)
- 回忆\(KMP\)构建\(fail\)数组的方式,我们按照顺序利用前面已得到的\(fail\)指针辅助构建。类似的,我们按照\(trie\)树的深度一层一层构建\(fail\)数组。
- 当要计算\(u\)的\(fail_u\)的时候我们找到\(fail_{fa_u}\)看它是否有对应\(fa_u\to^cu\)的\(c\)字符,如果没有我们像\(KMP\)一样,暴跳\(fail\)指针,直到找到\(c\)字符对应出边
- 这里还有一种更简便的构建\(fail\)指针的方式(实际上只是常数优化,思路是一样的)。具体来说,你对每个节点\(u\)的\(26\)条出边(对于文本只存在小写字母来说)无论出边有没有对应节点,都构建\(fail\)指针,这样在求节点\(v\)的\(fail\)指针的时候,我们只要把\(fail_{fa_v}\to^c x\)的\(x\)继承过来即可。
来源:https://www.cnblogs.com/shjrd-dlb/p/10964566.html