AC自动机
AC来自一个大佬的名字,并不是写了就可以自动AC的意思 XD
AC自动机是建立在trie树上的一种优化手段。trie在每次查询一个字符串时,如果在一个子树查不到就会回溯再查,效率会很低。我们考虑在给每个节点加一个如果查不到就跳转的指针fail,那么如果找不到的话直接跳转到fail就可以了。fail代表的是拥有该点的最大后缀的点的位置。
那么怎么寻找这个fail呢?因为我们要寻找最大后缀,深度就是一个比较重要的条件。于是我们bfs。注意了,设一个点now,tmp为now的子节点(从a到z任何一个),有以下推导:
(第一段使用结构体存储了点,第二段是用数组)
e[tmp].fail=e[e[now].fail].nxt[i];
fail[tmp]=ch[fail[now]][i];
首先我们知道trie是有一个0节点的。那么0连接的所有点的fail就连接的是0。如果我们接下来遍历每个点,对于任何点的fail都是其父节点的fail的子字符。因为是bfs,所以这样做一定是正确的而且一定会找到其最长后缀的点。
这里注意一下:如果now点有该字符是按照上面记录的。如果没有(ch[now][i]==0)就需要转移记录一下nxt:
e[now].nxt[i]=e[e[now].fail].nxt[i];
ch[now][i]=ch[fail[now]][i];
这样的话我们查询的时候只需要令now不断等于当前点的nxt就可以快速遍历啦~如果遍历到0,说明就end了。
让我们在打板子的时候顺便A一下洛谷的模板题:
P3808 【模板】AC自动机(简单版)
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<queue> using namespace std; const int maxn=1e6+10; char s[maxn]; int n,fail[maxn],ch[maxn][26],vis[maxn],val[maxn]; struct ac{ int cnt=0; inline void insert(char *s) { int now=0; for(;*s;s++) { int t=*s-'a'; if(!ch[now][t])ch[now][t]=++cnt; now=ch[now][t]; } val[now]++; } inline void build() { int now=0,t,tmp; queue<int> q; q.push(0); while(!q.empty()) { now=q.front();q.pop(); for(int i=0;i<26;i++) { t=ch[now][i]; if(t){ if(now) fail[t]=ch[fail[now]][i]; q.push(t); }else ch[now][i]=ch[fail[now]][i]; } } } inline int match(char *s) { int now,ans=0; for(now=0;*s;s++) { int t=*s-'a'; now=ch[now][t]; for(int tmp=now;tmp and !vis[tmp];tmp=fail[tmp]) ans+=val[tmp],vis[tmp]=1; } return ans; } }ac; int main() { scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%s",s); ac.insert(s); } ac.build(); scanf("%s",s); printf("%d\n",ac.match(s)); return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现