ac不是accepted的意思,而是作者的名字。
首先要学会ac自动机,就要先学会kmp算法和trie树(字典树)。
然而我为了写一篇ac自动机的博客,先写了trie的模板,还写了kmp的模板,所以在这里我就不介绍kmp和trie树了,就用简洁的文字讲述ac自动机的模板。
首先,我们先构造一棵trie树,对于每一个节点我们多设一个*fail指针,指向的位置----可以类比kmp的next数组。
若真的想要全面了解ac自动机的原理,可以搜寻一下其他的博客。
首先是一个结构体,链状的,跟trie树一样:(多一个fail)
struct node
{
node *fail;
node *son[26];
int sz;
node()
{
for(int i=0; i<=25; i++)
{
son[i]=NULL;
}
fail=NULL;
sz=0;
}
};
接着,也有一个类似于trie的Insert函数:
void my_insert(string ss)
{
node *p=&st;
for(int i=0; ison[ss[i]-'a']==NULL)
{
p->son[ss[i]-'a']=new node;
}
p=p->son[ss[i]-'a'];
}
p->sz++;
}
然后是构造fail的函数,就像kmp先gouz构造next函数一样。利用广搜,逐步对每一个节点的failjinxin进行赋值:
void my_find_fail()
{
queue q;
node *p;
node *temp;
q.push(&st);
while(!q.empty())
{
temp=q.front();
q.pop();
for(int i=0; i<=25; i++)
{
if(temp->son[i])
{
if(temp==&st)
{
temp->son[i]->fail=&st;
}
else
{
p=temp->fail;
while(p)
{
if(p->son[i])
{
temp->son[i]->fail=p->son[i];///不断的找fail,终止条件有两条
break; ///一。p==NULL;二。p->son[i]不为空,可以利用kmp的next来思考,但还是有点区别,具体为什么,可以看别人的博客。
}
p=p->fail;
}
if(p==NULL)
temp->son[i]->fail=&st;
}
q.push(temp->son[i]);
}
}
}
}
接下来是查找函数了:
就是先把要判断的单词插入到树里,然后再以需要判断的字符串来判断,就是把需要被判断的字符串,扫一遍所建的trie树:
void my_ac_automation(string ss)
{
node *p= &st;
int l=ss.size();
for(int i=0; ison[ss[i]-'a']&&p!=&st)
p=p->fail;
p=p->son[ss[i]-'a'];
if(!p)
p=&st;
node *temp=p;
while(temp!=&st)
{
if(temp->sz>=0)
{
counts+=temp->sz;
temp->sz=-1;
}
else
break;
temp=temp->fail;
}
}
}
这就是ac自动机模板的所有函数
下面给一道简单的模板提练一下,主要是用来检查模板是否正确。(洛谷3808)
这是一道简单的AC自动机模板题。
用于检测正确性以及算法常数。
为了防止卡OJ,在保证正确的基础上只有两组数据,请不要恶意提交。
管理员提示:本题数据内有重复的单词,且重复单词应该计算多次,请各位注意
给定n个模式串和1个文本串,求有多少个模式串在文本串里出现过。
输入格式:
第一行一个n,表示模式串个数;
下面n行每行一个模式串;
下面一行一个文本串。
输出格式:
一个数表示答案
输入样例#1: 复制
2 a aa aa
输出样例#1: 复制
2
subtask1[50pts]:∑length(模式串)<=10^6,length(文本串)<=10^6,n=1;
subtask2[50pts]:∑length(模式串)<=10^6,length(文本串)<=10^6;
下面是我自己用指针写的模板程序:
///ac自动机
#include
using namespace std;
struct node
{
node *fail;
node *son[26];
int sz;
node()
{
for(int i=0; i<=25; i++)
{
son[i]=NULL;
}
fail=NULL;
sz=0;
}
};
node st;
int counts;
void my_insert(string ss)
{
node *p=&st;
for(int i=0; ison[ss[i]-'a']==NULL)
{
p->son[ss[i]-'a']=new node;
}
p=p->son[ss[i]-'a'];
}
p->sz++;
}
void my_find_fail()
{
queue q;
node *p;
node *temp;
q.push(&st);
while(!q.empty())
{
temp=q.front();
q.pop();
for(int i=0; i<=25; i++)
{
if(temp->son[i])
{
if(temp==&st)
{
temp->son[i]->fail=&st;
}
else
{
p=temp->fail;
while(p)
{
if(p->son[i])
{
temp->son[i]->fail=p->son[i];///不断的找fail,终止条件有两条
break; ///一。p==NULL;二。p->son[i]不为空,可以利用kmp的next来思考,但还是有点区别,具体为什么,可以看别人的博客。
}
p=p->fail;
}
if(p==NULL)
temp->son[i]->fail=&st;
}
q.push(temp->son[i]);
}
}
}
}
void my_ac_automation(string ss)
{
node *p= &st;
int l=ss.size();
for(int i=0; ison[ss[i]-'a']&&p!=&st)
p=p->fail;
p=p->son[ss[i]-'a'];
if(!p)
p=&st;
node *temp=p;
while(temp!=&st)
{
if(temp->sz>=0)
{
counts+=temp->sz;
temp->sz=-1;
}
else
break;
temp=temp->fail;
}
}
}
int main()
{
int n;
cin>>n;
string s;
for(int i=1;i<=n;i++)
{
cin>>s;
//my_insert(s);
my_insert(s);
}
my_find_fail();
string sss;
cin>>sss;
my_ac_automation(sss);
cout<
还有一点需要我们注意:
我前几次用指针来写广搜,队列是这样定义的:queue
这就有问题了:我每次让队首出队之前,总是有temp=&q.front();
其实取出来的bing并不是原来的指针,电脑又生成了一个新的指针,这样我们接下来的操作就会有莫名的问题:
re...
这个问题真的究极可拍,希望用指针的朋友注意这一点。(用数组的巨佬可以不看这)
如果有问题,huan欢迎交流。
在这里推荐一篇博客:https://blog.csdn.net/creatorx/article/details/71100840