AC自动机
最近真是太颓了,做了一堆板子题,现在对一些知识点顺便来个总结记录
原理
大家应该都知道KMP和Trie树吧,不懂的可以看我博客或到网上自己动手寻找资料。AC自动机是一个很好的东西,这是因为它的名字很好它能够在有多个模式串的时候进行全文匹配,这十分方便地扩展了KMP的功能,实际上它的思路与原理与KMP十分地类似
首先,和KMP一样,我们要把根节点和根的儿子节点们的 Fail 函数都赋值为0,然后,我们考虑在Trie树上进行BFS,同时像KMP一样,不断地向前推,同时还要处理一个 Last 数组,用于保存每个节点沿失配边走所遇到的第一个单词节点,因为我们在找到一个模式串的时候,也许还会有许多其他的模式串也被匹配了,所以我们要不断地再沿着 Last 走即可
代码
#include<queue>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#define maxn 105
#define maxn1 1000005
#define maxn2 100005
#define sigma_size 26
using namespace std;
char txt[maxn1],tmp[maxn2];
int len1,n,size=1,f[maxn1],last[maxn1],ans=0;
bool vis[maxn];
//struct Trie{
int ch[maxn1][sigma_size];
int val[maxn1];
int id;
void Trie(){size=1;memset(ch[0],0,sizeof(ch[0]));}
int idx(char c){return c-'a';}
void insert(char* s,int v){
int u=0,len=strlen(s);
for(int i=0;i<len;i++){
int c=idx(s[i]);
if(!ch[u][c]){
memset(ch[size],0,sizeof(ch[size]));
val[size]=0;
ch[u][c]=size++;
}
u=ch[u][c];
}
val[u]=v;
}
//};
void init(){
f[0]=0;
queue<int> bfs;
for(int i=0;i<sigma_size;i++){
int u=ch[0][i];
if(!u)continue;
f[u]=0;
bfs.push(u);
last[u]=0;
}
while(!bfs.empty()){
int r=bfs.front();bfs.pop();
for(int i=0;i<sigma_size;i++){
int u=ch[r][i];
if(!u){
ch[r][i]=ch[f[r]][i];
continue;
}
bfs.push(u);
int v=f[r];
while(v&&!ch[v][i])v=f[v];
f[u]=ch[v][i];
last[u]=val[f[u]]?f[u]:last[f[u]];
}
}
}
void accumulate(int x){
if(x){
vis[val[x]]=1;
accumulate(last[x]);
}
return;
}
void AC_AM(){
init();
int j=0;
for(int i=0;i<len1;i++){
int u=idx(txt[i]);
//while(j&&!ch[j][u])j=f[j];
j=ch[j][u];
if(val[j])accumulate(j);
else if(last[j])accumulate(last[j]);
}
return;
}
int main(){
scanf("%s",txt);
len1=strlen(txt);
scanf("%d\n",&n);
Trie();
for(int i=0;i<n;i++){
scanf("%s",tmp);
insert(tmp,i+1);
}
AC_AM();
for(int i=1;i<=n;i++){
if(vis[i])ans++;
}
printf("%d",ans);
return 0;
}
大概就是这样
细节
我们可以进行一些小小的优化,比如我们可以把不存在的节点连接回存在的节点,即让它自动沿着 Fail 走,这样其实非常方便
在AC自动机上,或者说是在Trie树上,我们所谓的失配,就从KMP中的节点不匹配变化为了节点不存在,所以我们每次只要把 Σ (字母表)中的元素都枚举一遍即可
总结
没写的时候感觉很难,写完了以后其实也挺难,但实际上并不难