一.AC自动机的简介:
与KMP算法类似,AC自动机也是用来处理字符串匹配的问题。AC自动机是建立在KMP算法和Tire树的基础上的。(要有KMP和Tire树的基础,才能熟练掌握AC自动机)。
二.AC自动机的主要步骤:
构建一个AC自动机并用于匹配需要三个步骤:
①将所有的模式串构建一棵Tire树。
②对Tire上的所有节点构造前缀指针(fail数组)。(AC自动机的灵魂关键,我在这里卡了很久)
③利用前缀指针对主串进行匹配。
三.AC自动机的操作实现:
①建Tire树(与字典树的建树一模一样):
void insert()
{
int len=strlen(s),u=1;
for(int i=0;i<len;i++)
{
int c=s[i]-'A';
if(!ch[u][c]) ch[u][c]=++tot;
u=ch[u][c];
}
bo[u]=true;
}//日常操作...
②构建fail数组(关键):
void bfs()//通过BFS构建fail数组
{
for(int i=0;i<=25;++i)
ch[0][i]=1;//为了方便,将0的所有转移边都设为根节点1
que[1]=1;fail[1]=0;//若在根节点失配,则无法匹配字符串
for(int q1=1,q2=1;q1<=q2;++q1)
{
int u=que[q1];
for(int i=0;i<=25;++i)
{
if(!ch[u]) ch[u][i]=ch[fail[u]][i];//此处为优化
else
{
que[++q2]=ch[u][i];//若有这个儿子则其加队列中
int v=fail[u];
fail[ch[u][i]]=ch[v][i];//更新fail数组
}
}
}
}
③简单的字符串匹配:
int query()
{
int u=1;
for(int i=1;i<=m;++i)
{
u=ch[u][a[i]];
if(bo[u]) return 0;//匹配到返回 0 ,否则返回 1 ;
//注:如果是记录次数、数目之类的匹配可以在这里动手脚
}
return 1;
}
——By《一本通》
不得不说书上的代码真的是漏洞百出。
学完理论之后,拿一道简单(毒瘤)的模板题练练手——AC自动机模板
本题看似没有什么问题,但却需要高超的卡常技巧。不然500ms的时限过不了。
第一遍被卡常的代码(本地AC):
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<stack>
#include<stack>
#include<map>
#include<deque>
#include<cstdlib>
using namespace std;
const int N=1001005;
int ans=0,cnt=0,nex[N],ch[N][30],bo[N],que[N];
inline void make(char *s)
{
int u=1,len=strlen(s);
for(int i=0;i<len;++i)
{
int c=s[i]-'a';
if(!ch[u][c])
{
ch[u][c]=++cnt;
memset(ch[cnt],0,sizeof(ch[cnt]));
}
u=ch[u][c];
}
bo[u]++;
return ;
}
inline void bfs()
{
for(int i=0;i<=25;++i)
{
ch[0][i]=1;
}
que[1]=1;nex[1]=0;
for(int q1=1,q2=1;q1<=q2;++q1)
{
int u=que[q1];
for(int i=0;i<=25;++i)
{
if(!ch[u][i]) ch[u][i]=ch[nex[u]][i];
else
{
que[++q2]=ch[u][i];
int v=nex[u];
nex[ch[u][i]]=ch[v][i];
}
}
}
return;
}
inline void find(char *s)
{
int u=1,len=strlen(s),c,k;
for(int i=0;i<=len;++i)
{
c=s[i]-'a';
k=ch[u][c];
while(k>1)
{
ans+=bo[k];
bo[k]=0;
k=nex[k];
}
u=ch[u][c];
}
return;
}
int main()
{
int n;
char s[N<<1];
ans=0;cnt=1;
memset(bo,0,sizeof(bo));
for(int i=1;i<26;++i) ch[0][i]=1,ch[1][i]=0;
scanf("%d",&n);
for(int i=1;i<=n;++i)
{
scanf("%s",s);
make(s);
}
bfs();
scanf("%s",s);
find(s);
printf("%d
",ans);
return 0;
}
看了题解的高超卡常代码(AC):
#include<bits/stdc++.h>
#define N 500010
using namespace std;
queue<int>q;
struct Aho_Corasick_Automaton//结构体存函数
{
int ch[N][26],bo[N],fail[N],cnt;//......
void insert(char *s)//与字典树一模一样的建树方式
{
int len=strlen(s);
int now=0;
for(int i=0;i<len;i++)
{
int v=s[i]-'a';
if(!ch[now][v]) ch[now][v]=++cnt;
now=ch[now][v];
}
bo[now]++;
}
void build()//预处理fail数组
{
for(int i=0;i<26;i++)
{
if(ch[0][i])
{
fail[ch[0][i]]=0;
q.push(ch[0][i]);
}
}
while(!q.empty())
{
int u=q.front();q.pop();
for(int i=0;i<26;i++)
{
if(ch[u][i])
{
fail[ch[u][i]]=ch[fail[u]][i];
q.push(ch[u][i]);
}
else
ch[u][i]=ch[fail[u]][i];
}
}
}
int query(char *s)//查询模式串在文本串中出现的次数
{
int len=strlen(s);
int now=0,ans=0;
for(int i=0;i<len;i++)
{
now=ch[now][s[i]-'a'];
for(int t=now;t&&~bo[t];t=fail[t])
{
ans+=bo[t];//有查到这个模式串,就ans总数++
bo[t]=-1;//标记查到过(避免重复计算)
}
}
return ans;//返回查找到的模式串的次数
}
}AC;
int n;//要插入模式串的组数
char p[1000005];//定义p字符数组
int main()
{
scanf("%d",&n);//输入n
for(int i=1;i<=n;i++)
{
scanf("%s",p);
AC.insert(p);//输入并插入模式串
}
AC.build();//插入模式串完成后,开始预处理fail数组
scanf("%s",p);//输入文本串
int ans=AC.query(p);//匹配
printf("%d
",ans);//输出
return 0;
}