今天刚刚学习了AC自动机,刷了个模板题。
前置知识:trie树和KMP。
其实说实话吧,KMP在AC自动机里的运用其实就是失配指针。
失配指针其实就是在trie树上寻找最长的后缀,采用bfs的顺序是为了保证前后顺序的不变,当你在当前节点的时候知道你的上一层节点的失配指针的指向,在匹配中就可以节约大量时间来进行多模式串的匹配。
下附代码
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#define N 510001
#define M 1100001
using namespace std;
int t,n,trie[N][26],tot=1,nxt[N],f[N],bo[N],ans;
char w[51],que[M];
void ins()
{
int r=1,x,len=strlen(w+1);
for(int i=1;i<=len;i++)
{
x=w[i]-'a';
if(!trie[r][x]) trie[r][x]=++tot;
r=trie[r][x];
}
bo[r]++;
return;
}
void getfail()
{
int q1=1,q2=1,r;
f[1]=1,nxt[1]=0;
for(int i=0;i<26;i++) trie[0][i]=1;
for(;q1<=q2;++q1)
{
r=f[q1];
for(int i=0;i<26;i++)
{
if(!trie[r][i]) trie[r][i]=trie[nxt[r]][i];
else
{
f[++q2]=trie[r][i];
nxt[trie[r][i]]=trie[nxt[r]][i];
}
}
}
return;
}
void fin()
{
int r=1,x,len=strlen(que+1),k;
for(int i=1;i<=len;i++)
{
x=que[i]-'a',k=trie[r][x];
while(k>1) ans+=bo[k],bo[k]=0,k=nxt[k];
r=trie[r][x];
}
return;
}
int main()
{
scanf("%d",&t);
for(int i=1;i<=t;i++)
{
scanf("%d",&n);
for(int j=1;j<=n;j++) scanf("%s",w+1),ins();
scanf("%s",que+1);
getfail(),fin();
printf("%d\n",ans),ans=0;
memset(bo,0,sizeof(bo));
memset(trie,0,sizeof(trie));
}
return 0;
}
我们来看到getfail的函数部分,f是一个队列,用来存储下一层。如果当前节点没有失配指针的话,那么就指向默认的第一层(trie的根),如果有,那么加入队列,而且存储他的失配指针。
for(int i=0;i<26;i++) trie[0][i]=1;记住一定要初始化,否则节点会重复加入队列,造成死循环。
且AC自动机的题大多是多组数据,记得清零,初始化。