AC自动机:
与kmp的区别:AC自动机是多模式串匹配,kmp是单模式串匹配
思想:字典树和kmp的结合,还是和字典树一样建树,但是要加入个fail指针来实现和kmp的next类似的回溯功能。
fail指针的回溯:对下图的解释,每个结点都有编号(图中原点里面的就是编号),fail指针的指向(图中的虚线)。搞懂fail指针是指向的是什么,fail指针指向的是父节点相同的同值节点(根节点视为与任何节点相同)。fail指针是如何指向的,就拿91号来说,91号对应的是E字母,它就问它的父结点90号的fail指向的是谁,90号的fail指向的是74号,然后就问74号有没有值为E的儿子,有,就是76号结点,于是91号结点的fail指针就指向76号。总结,fail指针的建立,就是从根节点开始对树进行bfs,查到一个点的时候,就向着这个点的父结点的fail指针对应的点询问,询问fail指针指向的点有没有和它一样的儿子,有的话就让查到的点的fail指针指向,没有的话继续找fail指针指向点的fail指针指向的下一个。
模板代码:
struct AC_auto
{
struct node
{
int nxt[26];//26个字母
int fail,cnt;//fail指针,cnt以该字母结尾的单词个数
}tree[max_tot];
int sz;//节点个数,标记
std::queue<int>que;
void init()//初始化
{
while(!que.empty())
{
que.pop();
}
for(int i=0;i<max_tot;i++)
{
memset(tree[i].nxt,0,sizeof(tree[i].nxt));
tree[i].fail=tree[i].cnt=0;
}
sz=1;//初始化值为1
}
void insert_tree(char*s)//插入
{
int n=strlen(s);
int now=0;
for(int i=0;i<n;i++)
{
char c=s[i];
if(!tree[now].nxt[c-'a'])//如果结点不存在
{
tree[now].nxt[c-'a']=sz++;//添加一个结点
}
now=tree[now].nxt[c-'a'];//下一个结点的位置
}
tree[now].cnt++;//单词个数++
//printf("%c %d\n",s[n-1],now);
}
void build()//建立,get_fail
{
tree[0].fail=-1;//根节点的fail为-1
que.push(0);
while(!que.empty())
{
int u=que.front();
que.pop();
for(int i=0;i<26;i++)
{
if(tree[u].nxt[i])
{
if(u==0)
{
tree[tree[u].nxt[i]].fail=0;//父结点是根节点的,fail直接为0
}
else
{
int v=tree[u].fail;
while(v!=-1)
{
if(tree[v].nxt[i])
{
tree[tree[u].nxt[i]].fail=tree[v].nxt[i];
break;
}
v=tree[v].fail;
}
if(v==-1)
{
tree[tree[u].nxt[i]].fail=0;
}
}
que.push(tree[u].nxt[i]);
}
}
}
}
int Get(int u)
{
int res=0;
while(u)//fail指向的字母在单词中到它为止的前缀,是指向它前缀包含的,可以理解一下
{
res=res+tree[u].cnt;
tree[u].cnt=0;
u=tree[u].fail;
}
return res;
}
int match(char*s)
{
int n=strlen(s);
int res=0,now=0;
for(int i=0;i<n;i++)
{
char c=s[i];
if(tree[now].nxt[c-'a'])
{
now=tree[now].nxt[c-'a'];
}
else
{
int p=tree[now].fail;
while(p!=-1&&tree[p].nxt[c-'a']==0)
{
p=tree[p].fail;
}
if(p==-1)
{
now=0;
}
else
{
now=tree[p].nxt[c-'a'];
}
}
if(tree[now].cnt)
{
res=res+Get(now);
}
}
return res;
}
}ac;
HDU-2222 Keywords Search:
题意:给你t个case,每个case有n个单词,然后给你一个字符串,问在这个字符串中有多少个单词出现过。
思路:裸AC自动机
代码:
#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
#include <stack>
#include <cstdlib>
#include <map>
#include <queue>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
using namespace std;
const int maxn=1000005;
const int max_tot=500005;
struct AC_auto
{
struct node
{
int nxt[26];//26个字母
int fail,cnt;//fail指针,cnt以该字母结尾的单词个数
}tree[max_tot];
int sz;//节点个数,标记
std::queue<int>que;//
void init()//初始化
{
while(!que.empty())
{
que.pop();
}
for(int i=0;i<max_tot;i++)
{
memset(tree[i].nxt,0,sizeof(tree[i].nxt));
tree[i].fail=tree[i].cnt=0;
}
sz=1;//初始化值为1
}
void insert_tree(char*s)//插入
{
int n=strlen(s);
int now=0;
for(int i=0;i<n;i++)
{
char c=s[i];
if(!tree[now].nxt[c-'a'])//如果结点不存在
{
tree[now].nxt[c-'a']=sz++;//添加一个结点
}
now=tree[now].nxt[c-'a'];//下一个结点的位置
}
tree[now].cnt++;//单词个数++
//printf("%c %d\n",s[n-1],now);
}
void build()//建立,get_fail
{
tree[0].fail=-1;//根节点的fail为-1
que.push(0);
while(!que.empty())
{
int u=que.front();
que.pop();
for(int i=0;i<26;i++)
{
if(tree[u].nxt[i])
{
if(u==0)
{
tree[tree[u].nxt[i]].fail=0;//父结点是根节点的,fail直接为0
}
else
{
int v=tree[u].fail;
while(v!=-1)
{
if(tree[v].nxt[i])
{
tree[tree[u].nxt[i]].fail=tree[v].nxt[i];
break;
}
v=tree[v].fail;
}
if(v==-1)
{
tree[tree[u].nxt[i]].fail=0;
}
}
que.push(tree[u].nxt[i]);
}
}
}
}
int Get(int u)
{
int res=0;
while(u)
{
res=res+tree[u].cnt;
tree[u].cnt=0;
u=tree[u].fail;
}
return res;
}
int match(char*s)
{
int n=strlen(s);
int res=0,now=0;
for(int i=0;i<n;i++)
{
char c=s[i];
if(tree[now].nxt[c-'a'])
{
now=tree[now].nxt[c-'a'];
}
else
{
int p=tree[now].fail;
while(p!=-1&&tree[p].nxt[c-'a']==0)
{
p=tree[p].fail;
}
if(p==-1)
{
now=0;
}
else
{
now=tree[p].nxt[c-'a'];
}
}
//printf("%d %d\n",now,tree[now].cnt);
if(tree[now].cnt)
{
res=res+Get(now);
//printf("%d\n",now);
}
}
return res;
}
}ac;
char ss[maxn];
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
int n;
scanf("%d",&n);
ac.init();
while(n--)
{
scanf("%s",ss);
ac.insert_tree(ss);
}
ac.build();
scanf("%s",ss);
int ans=ac.match(ss);
printf("%d\n",ans);
}
return 0;
}