【字符串处理】AC自动机知识点&代码
代码:

#include<iostream> #include<vector> #include<cstdio> #include<queue> #include<map> #include<cstdlib> #include<cmath> #include<algorithm> #include<set> #include<cstring> using namespace std; typedef long long ll; const ll INF=99999999; const int MAXN=1000024; int trie[MAXN][27]; //i到j的编号 int val[MAXN]; //代表这个节点有多少单词 int fail[MAXN]; //fail指针,意为失配时去的位置 int cnt; queue<int> q; void ins(string str){ //添加单词 int last=0; //代表着多次失配之后最后跳到的地方 for(int i=0;i<(int)str.length();i++){ int v=str[i]-'a'; if(!trie[last][v]) trie[last][v]=++cnt; //如果没有对应的这个节点的话,就加上(标记) last=trie[last][v]; //将上次的编号记住 } val[last]++; //单词数+1 } void build(){ for(int i=0;i<26;i++) //遍历与根节点连接的点 if(trie[0][i]){ //如果有这个字母的儿子 fail[trie[0][i]]=0; //将fail指针指向根 q.push(trie[0][i]); //加入搜索队列 } while(!q.empty()){ int u=q.front(); q.pop(); for(int i=0;i<26;i++){ //枚举其每个儿子 if(trie[u][i]){ //如果有这个节点 fail[trie[u][i]]=trie[fail[u]][i]; //fail指针指向父节点(当前节点)的fail指针指向的节点的相同字母节点 q.push(trie[u][i]); //加入这个点 } else{ trie[u][i]=trie[fail[u]][i]; //没有这个点的话将其等同于父节点(当前节点)的fail指针的相同字母节点 } } } } int query(string str){ int last=0,ans=0; for(int i=0;i<(int)str.length();i++){ last=trie[last][str[i]-'a']; //获得当前字母,当前位置的编号 for(int j=last;j&&~val[j];j=fail[j]) { //只要没有结尾,就按照fail路线走 ans+=val[j]; //加上以这个节点结尾的单词书亮 val[j]=-1; //已经拿走了,所以没有了 } } return ans; } int main() { ios_base::sync_with_stdio(false); cin.tie(0); int n; cin>>n; string st; for(int i=0;i<n;i++){ cin>>st; ins(st); //添加单词 } build(); //初始化 cin>>st; cout<<query(st); //输出 return 0; }
0.容器
大部分人是用struct或者class实现内部的函数,但是作为考场上最倩的仔(雾,我决定使用数组存。
(很不理解为什么要用struct存,OI又不是写工程)
1 2 3 4 | int trie[MAXN][27]; //代表从前、后的编号 int val[MAXN]; //代表这个节点有多少单词 int fail[MAXN]; //fail指针,意为失配时去的位置 int cnt; //当前编号 |
MAXN是数据的范围,是一个常量
1.插入
插入相当于从根一直走到最后一个字母的位置,没有就插入并赋予它一个新编号
1 2 3 4 5 6 7 8 9 | void ins(string str){ //添加单词 int last=0; //代表着多次失配之后最后跳到的地方 for ( int i=0;i<( int )str.length();i++){ int v=str[i]- 'a' ; if (!trie[last][v]) trie[last][v]=++cnt; //如果没有对应的这个节点的话,就加上(标记为新的节点,赋予其新编号) last=trie[last][v]; //将这次的编号记住,以便下次使用 } val[last]++; //单词数+1 } |
2.Build
Build的是Fail指针:
先把所有与根节点连接的点的Fail指针指向根,然后将它们加入队列
然后BFS整棵树:
取出队首,然后将其儿子的Fail指针指向父亲的fail指针指向的相同字母的节点
赋予队首不存在的子节点父亲节点的Fail指针指向的相同字母的节点的编号
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | void build(){ for ( int i=0;i<26;i++) //遍历与根节点连接的点 if (trie[0][i]){ //如果有这个字母的儿子 fail[trie[0][i]]=0; //将fail指针指向根 q.push(trie[0][i]); //加入搜索队列 } while (!q.empty()){ int u=q.front(); q.pop(); for ( int i=0;i<26;i++){ //枚举其每个儿子 if (trie[u][i]){ //如果有这个节点 fail[trie[u][i]]=trie[fail[u]][i]; //fail指针指向父节点(当前节点)的fail指针指向的节点的相同字母节点 q.push(trie[u][i]); //加入这个点 } else { trie[u][i]=trie[fail[u]][i]; //没有这个点的话将其等同于父节点(当前节点)的fail指针的相同字母节点 } } } } int query(string str){ int last=0,ans=0; for ( int i=0;i<( int )str.length();i++){ last=trie[last][str[i]- 'a' ]; //获得当前字母,当前位置的编号 for ( int j=last;j&&~val[j];j=fail[j]) { //只要没有结尾,就按照fail路线走 ans+=val[j]; //加上以这个节点结尾的单词书亮 val[j]=-1; //已经拿走了,所以没有了 } } return ans; } |
3.顺序
输入单词
ins
build
输出query
1 2 3 4 5 6 7 8 9 10 | int n; cin>>n; string st; for ( int i=0;i<n;i++){ cin>>st; ins(st); //添加单词 } build(); //初始化 cin>>st; cout<<query(st); //输出 |
本模版可以在洛谷P3808 https://www.luogu.org/problemnew/show/P3808提交,已通过
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!