初学AC自动机,在网上查了半天资料,没看懂,最后竟还是看了一篇别人推荐的英语原文,才渐渐懂了。学习AC自动机,首先我们要有字典树和KMP的基础,字典树作为数据存储的结构基础,而KMP则为算法基础(KMP可理解为特殊的AC自动机,此时所建的字典树为一叉树)。
算法思想懂了后,就开始做这道网友极力推荐的AC自动机模板题了,哎,虽然说思想懂了,但真真实现起来也花费了差不多一天的时间。。无论如何,这都是自己做的第一道AC自动机的题目,还是勉励一下自己吧
以下是讲解AC自动机的一篇比较好的文章,虽然是英文的,但真的写得很好,强烈建议看一下 http://www.cs.uku.fi/~kilpelai/BSA05/lectures/slides04.pdf
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int maxn=1000005;
struct node
{
int count;
node *next[26];
node *fail;
}memory[maxn],*root;
int cur;
node *q[maxn];
node * build_node()//添加新的节点
{
node *p=&memory[cur];
p->count=0;
for(int i=0;i<26;i++)
p->next[i]=NULL;
cur++;
return p;
}
void insert_tree(char *s)//添加单词到字典树中
{
int len,index;
node *p=root;
len=strlen(s);
for(int i=0;i<len;i++)
{
index=s[i]-'a';
if(p->next[index]==NULL)
p->next[index]=build_node();
p=p->next[index];
}
p->count++;
}
void BFS()//建失败指针
{
node *p,*u,*v;
int f,r;
root->fail=root;
f=r=0;
for(int i=0;i<26;i++)//先将第一层节点入栈,fail指针指向root
if(root->next[i]!=NULL)
{
q[r++]=root->next[i];
root->next[i]->fail=root;
}
while(f!=r)
{
p=q[f++];
for(int i=0;i<26;i++)
{
u=p->next[i];
if(u!=NULL)//p->u有边
{
q[r++]=u;//入栈
v=p->fail;
while(v!=root&&v->next[i]==NULL)//找最长后缀
v=v->fail;
u->fail=(v->next[i]==NULL)?root:v->next[i];//判断一下最长后缀是否存在
}
}
}
}
int AC_AUTO(char *s)
{
int len,ans,index;
node *p,*tmp;
len=strlen(s);
p=root;
ans=0;
for(int i=0;i<len;i++)
{
index=s[i]-'a';
while(p->next[index]==NULL&&p!=root)//若匹配失败,则找到失败指针指向的节点,直到成功匹配或到了root
p=p->fail;
if(p->next[index]!=NULL)
p=p->next[index];
//此处为找出当前状态下的所有模式串,记录并将找到的模式串标记,去重
tmp=p;
while(tmp!=root)
{
ans+=tmp->count;
tmp->count=0;
tmp=tmp->fail;
}
}
return ans;
}
int main()
{
int t,n;
char word[55],text[maxn];
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
cur=0;
root=build_node();
while(n--)//建字典树
{
scanf("%s",word);
insert_tree(word);
}
BFS();//初始化失败指针
scanf("%s",text);
printf("%d\n",AC_AUTO(text));//匹配模式串
}
return 0;
}