acam学习笔记
部分内容摘录自oi wiki。
AC 自动机是是以 Trie 的结构为基础,结合 KMP 的思想建立的自动机,用于解决多模式匹配(在文本串中匹配一系列模式串)等任务。其时间复杂度与串的总长度成正比。
可以感性理解为在trie树上跑kmp。
概述
简单来说,建立一个 AC 自动机有两个步骤:
1.基础的 Trie 结构:将所有的模式串构成一棵 Trie;
2.KMP 的思想:对 Trie 树上所有的结点构造失配指针。
建立完毕后,就可以利用它进行多模式匹配。
fail指针
类似于kmp中的nxt指针,用于在当前节点失配后,找到下一个最长的匹配位置。即尝试取当前字符串的一段后缀,观察它能否在字典树上换个位置匹配,fail指针即指向这个最长后缀的位置。
构建fail指针时,可以直接暴力的去跳,即在节点x,如果不存在一条字符c的出边,就不停地跳它的fail指针,直到根节点停止,或者任意一个位置存在c的出边,链接fail指针。
trie图优化
这样做一次时间复杂度就是\(O(n)\)的,考虑优化。
我们发现,如果父节点的fail指针位置存在一条字符c的出边,则当前点的fail指针直接指向fail(fa).nex[c]一定是不劣的。
如果不存在呢?
考虑对于每一个点,都处理出每一种字符的出边应该指向哪里。那么其实就是要求遍历到点x时,x的fail指针处的一切信息全部已知,可以直接通过它来转移。
注意到fail指针一定是从深层指向浅层,所以可以通过bfs实现。
这就是trie图优化。
但注意trie图所连出的边,本质上并不属于ac自动机,只是一种快速转移的优化。
拓扑优化
和拓扑没有关系,不知道为什么叫这个名字。
用于统计每个模式串在文本串中出现的次数。
注意到对于每个串,如果它出现了一次,则它的所有后缀都会出现一次,这个无法在字典树上直接处理(但前缀显然可以)。
发现后缀其实就是不断的跳fail指针即可,但这样做时间复杂度就寄了。
考虑优化。
发现如果单独提炼出点和fail指针,则会构成一棵树,对于树上的每个点,它能通过fail指针转移到的状态其实就是它的所有祖先。
所以我们每次访问到一个点就标记,最后dfs统一求和即可。
总结
个人观点:ac自动机本质上就是两棵树的叠加:一棵字典树用于记录字符串,一棵fail树用于状态转移,做题的时候分清两种操作即可。
进阶一点之后会出现acam和dp结合。