AC 自动机学习笔记
preface
第一次写 ACAM 模版是 2023.7.02,现在重新回顾了一下,还是有不少新的理解的,或者说一些概念更加清晰了。
1.引入
思考这样一个问题:
给若干模式串,求询问串中出现了多少个模式串。
暴力肯定是一一比对,复杂度是
回想一下 kmp 算法解决的题目,暴力匹配也是
首先可以把模式串都搬到 trie 树上,这样各个模式串相互的前缀信息就清楚了。显然 trie 树上每个节点都对应了一条到根节点的前缀。我们在 trie 树上也维护每个节点的最长相等前后缀,但这里的最长相等前后缀就不局限于这一条到根节点的前缀了,而是整棵 trie 树。那么现在可以简单理解为把 kmp 算法搬到 trie 树就有了 ACAM 算法。
2.实现
插入和 trie 树一样,不讲。
与 kmp 算法相同,ACAM 也有失配数组
void getfail() {
std::queue<int> q;
for(int i = 0; i < 26; i++) if(tr[0][i]) q.push(tr[0][i]);
while(!q.empty()) {
int u = q.front(); q.pop();
for(int i = 0; i < 26; i++) {
if(tr[u][i]) fail[tr[u][i]] = tr[fail[u]][i], q.push(tr[u][i]);
else tr[u][i] = tr[fail[u]][i];
}
}
}
做到这一步就算建出了 ACAM 了,那么前面的题怎么做呢。
3.作用
尝试在 ACAM 上走询问串,假如走到节点
这样做是不重不漏的。因为这个过程相当于你固定了询问串上的右端点,查询从右端点开始有没有这样一段后缀是模式串。
更进一步,用
上面的暴力跳祖先复杂度太高,怎么优化?这个过程不就是求根到节点的链和嘛,求一下
4.延伸
假如题目变成这样:
给若干模式串,求模式串中出现了多少个询问串。(保证询问串在模式串中出现过)
可以在 fail 树上考虑这个问题。因为题目的限制,他一定对应一个树上一个节点,那么它子树中的所有节点一定包含它(换句话说,如果子树中节点失配的话,一定会跳到它),所以就转化为子树求和做了。
5.思考
其实仔细想想,kmp 不就是一个模式串的 ACAM 吗?它的 trie 树是一条链,本质是相同的。
fail 树的包含关系非常重要。
如果加入一些修改操作,都有 fail 树了,就用数据结构维护树上问题呗。通常可以用 dfs 序转化为序列问题。
比如引入的问题中,如果加删字符串(前提是 ACAM 建出来了),就是一个单点加,求链和的问题,然后这个问题可以用树上差分转化为更简单的子树加,求单点,用树状数组维护。
延伸的问题里,同样的操作,只是修改变成了若干个单点修改,询问变成了求子树和。
ACAM 又提供了一个很好的状态表示,可以和 DP 结合。套路的设
6.习题
P3041 [USACO12JAN] Video Game G
经典 ACAM + DP
设
复杂度
经典的数据结构维护 ACAM 的题目,用上引出中的经典转化,需要实现子树加,单点求值。
ACAM + DP
需要发现
CF1202E You Are Given Some Strings...
考虑枚举断点
前者是经典的 fail 树问题,后者翻转做一遍同样的事即可。
考虑离线,然后拆贡献,询问
一边插入(若干单点加),一边计算答案即可(子树求和)。
上一题反过来,一下子不好做了。
如果是暴力的话,每次都要跑一遍
长度小跑暴力即可,长度大需要换一种角度。
如果考虑现在变成枚举每一个长度大的
前面的做法复杂度是
总复杂度
二进制分组在线 ACAM
应该是无法做到在线 ACAM,只能考虑每次重构 fail 树。考虑二进制分组,将当前的
合并操作和线段树合并的方法类似。需要注意的是插入时要在 trie 树上插入,与 ACAM 的数组要分为两个,因为建 ACAM 的时候改变了 trie 树的结构。
每个串最多合并
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具