学习AC自动机之前还需要一点其他的知识的基础:KMP,trie树(字典树)。
首先我们要知道AC自动机是干什么用的。
大家都知道KMP算法是求单字符串对单字符串的匹配使用的。
那么,多个字符串在一个字符串上的匹配怎么办?
如果每次对一个需要匹配的串求一次next数组,然后一次次去匹配,显然,这样变得很慢很慢了。
那么,我们要如何解决这类问题呢?
就是AC自动机。
先看一下它的定义:Aho-Corasick automation,该算法在1975年产生于贝尔实验室,是著名的多模匹配算法。
正式开始,我们来讲解AC自动机。
第一步:把它们构建成一棵Trie树。
第二步(也就是最重要的一步):构建失配指针。
这一步很KMP算法中的next数组是类似的,通过跳转来省略重复的检查。
我们先看看这个指针是要干什么吧。每次沿着Trie树匹配,匹配到当前位置没有匹配上时,直接跳转到失配指针所指向的位置继续进行匹配。
Trie树的失配指针是指向:沿着其父节点 的 失配指针,一直向上,直到找到拥有当前这个字母的子节点 的节点 的那个子节点。
感觉听起来很复杂吧,但是自己画一下图就好了。
值得一提的是,第二层的所有节点的失配指针,都要直接指向Trie树的根节点。
第三步:匹配。首先,指针指向根节点,依次读入单词,检查是否存在这个子节点,然后指针跳转到子节点,如果不存在,直接跳转到失配指针即可。
例题:HDU2222。
题意:问文本串中用几个目标串出现。
Code:
#include<iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#include<cstdio>
#include<queue>
#define N 1000005
using namespace std;
int cnt;
char s[N];
struct tree
{
int fail,son[26],end;
}AC[N];
inline int read()
{
int x=0,f=1;char s=getchar();
while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
while(s<='9'&&s>='0'){x=x*10+s-'0';s=getchar();}
return x*f;
}
void insert(char s[])
{
int len=strlen(s);
int now=0;
for(int i=0;i<len;i++)
{
if(!AC[now].son[s[i]-'a'])
AC[now].son[s[i]-'a']=++cnt;
now=AC[now].son[s[i]-'a'];
}
AC[now].end+=1;
}
void Get_fail()
{
queue<int> Q;
for(int i=0;i<=25;i++)
if(AC[0].son[i])
{
AC[AC[0].son[i]].fail=0;
Q.push(AC[0].son[i]);
}
while(!Q.empty())
{
int u=Q.front();
Q.pop();
for(int i=0;i<=25;i++)
if(AC[u].son[i])
{
AC[AC[u].son[i]].fail=AC[AC[u].fail].son[i];
Q.push(AC[u].son[i]);
}else
AC[u].son[i]=AC[AC[u].fail].son[i];
}
}
int AC_Query(char s[])
{
int len=strlen(s);
int now=0,ans=0;
for(int i=0;i<len;i++)
{
now=AC[now].son[s[i]-'a'];
for(int t=now;t&&AC[t].end!=-1;t=AC[t].fail)
{
ans+=AC[t].end;
AC[t].end=-1;
}
}
return ans;
}
int main()
{
int T=read();
while(T--)
{
cnt=0;
int n=read();
memset(AC,0,sizeof(AC));
for(int i=1;i<=n;i++)
{
scanf("%s",s);
insert(s);
}
AC[0].fail=0;
Get_fail();
scanf("%s",s);
printf("%d\n",AC_Query(s));
}
return 0;
}
例题:
洛谷3796。
题意:求文本串中每个目标串最多出现几次。
Code:
#include<iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#include<cstdio>
#include<queue>
#define N 1000005
using namespace std;
int cnt;
char s[N][105],ch[N];
struct tree
{
int fail,son[26],end;
}AC[N];
struct node
{
int num,pos;
}ans[N];
int cmp(node x,node y)
{
if(x.num!=y.num)return x.num>y.num;
else return x.pos<y.pos;
}
inline int read()
{
int x=0,f=1;char s=getchar();
while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
while(s<='9'&&s>='0'){x=x*10+s-'0';s=getchar();}
return x*f;
}
void insert(char s[],int num)
{
int len=strlen(s);
int now=0;
for(int i=0;i<len;i++)
{
if(!AC[now].son[s[i]-'a'])
AC[now].son[s[i]-'a']=++cnt;
now=AC[now].son[s[i]-'a'];
}
AC[now].end=num;
}
void Get_fail()
{
queue<int> Q;
for(int i=0;i<=25;i++)
if(AC[0].son[i])
{
AC[AC[0].son[i]].fail=0;
Q.push(AC[0].son[i]);
}
while(!Q.empty())
{
int u=Q.front();
Q.pop();
for(int i=0;i<=25;i++)
if(AC[u].son[i])
{
AC[AC[u].son[i]].fail=AC[AC[u].fail].son[i];
Q.push(AC[u].son[i]);
}else
AC[u].son[i]=AC[AC[u].fail].son[i];
}
}
void AC_Query(char s[])
{
int len=strlen(s);
int now=0;
for(int i=0;i<len;i++)
{
now=AC[now].son[s[i]-'a'];
for(int t=now;t;t=AC[t].fail)
ans[AC[t].end].num++;
}
}
int main()
{
int n=read();
while(n)
{
cnt=0;
memset(AC,0,sizeof(AC));
for(int i=1;i<=n;i++)
{
scanf("%s",s[i]);
insert(s[i],i);
ans[i].num=0;
ans[i].pos=i;
}
AC[0].fail=0;
Get_fail();
scanf("%s",ch);
AC_Query(ch);
sort(ans+1,ans+1+n,cmp);
printf("%d\n",ans[1].num);
printf("%s\n",s[ans[1].pos]);
for(int i=2;i<=n;i++)
if(ans[i].num==ans[i-1].num)
printf("%s\n",s[ans[i].pos]);
else break;
n=read();
}
return 0;
}