AC自动机
AC自动机
其实是trie树的改图。每次状态转移在末尾增添一个字符。
其使用失配指针 指向自动机中自己的最长后缀,以达到多模匹配的效果。
模板题 代码:
其实也不多说了,就是trie加一个fail指针。
其中fail指针如何求?我们遍历全图,当遍历到一条链的第二个点时,我们在初始状态位置向下找到是否有与第二条边相同的字符,然后更新fail即可。其他情况差不多,只不过是从上一次的fail更新接下来的fail,具体见代码。
struct AC_auto_machine{
int ch[N][26],tot;
ll val[N];
int fail[N];
int deg[N];
inline int ins(char *s){
int len=strlen(s+1),p=0;
for(int i=1;i<=len;++i){
int c=s[i]-'a';
if(!ch[p][c]){
ch[p][c]=++tot;
}
p=ch[p][c];
}
val[p]=1;
return p;
}
inline void build(){
static std::queue<int>q;
while(!q.empty() ) q.pop();
for(int i=0;i<26;++i){
if(ch[0][i]) q.push(ch[0][i]);
}
while(!q.empty() ){
int u=q.front();q.pop();
for(int i=0;i<26;++i){
if(!ch[u][i]) ch[u][i]=ch[fail[u] ][i];
else{
fail[ch[u][i] ]=ch[fail[u] ][i],q.push(ch[u][i]);
++deg[fail[ch[u][i] ] ];
}
}
}
}
inline void query(char *s){
int len=strlen(s+1),p=0;
for(int i=1;i<=len;++i){
int c=s[i]-'a';
p=ch[p][c];
++cnt[p];
}
}
inline void topo(){
static std::queue<int>q;
while(!q.empty() ) q.pop();
for(int i=1;i<=tot;++i){
if(!deg[i]) q.push(i);
}
while(!q.empty() ){
int u=q.front();q.pop();
int v=fail[u];
--deg[v];
cnt[v]+=cnt[u];
if(!deg[v]) q.push(v);
}
}
};
发现有一些地方写得和上面不符合?算是加了些许优化吧。(下面有些提到的上面并没有完全展现出来)
优化1
发现如果匹配fail时,fail上存在加入一条新边得到最长后缀,但是其本身在图上没有这条边可以走。我们直接把它连边是一种不错的解决方案。本质上是当场失配当场跳fail。
优化2
拓扑排序统一处理fail链后缀和(从最大后缀到最小后缀)。把大的处理完再把权值扔给小的即可。
优化3
统计fail链前缀和。上面那个题没有怎么用。
这道题用了这个优化。还有这个同时也是二进制分组维护动态的静态数据结构,有兴趣可以了解一下。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具