【字符串处理】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又不是写工程)
int trie[MAXN][27]; //代表从前、后的编号 int val[MAXN]; //代表这个节点有多少单词 int fail[MAXN]; //fail指针,意为失配时去的位置 int cnt; //当前编号
MAXN是数据的范围,是一个常量
1.插入
插入相当于从根一直走到最后一个字母的位置,没有就插入并赋予它一个新编号
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指针指向的相同字母的节点的编号
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
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提交,已通过