AC自动机-hdu2222Keywords Search
http://acm.hdu.edu.cn/showproblem.php?pid=2222
Fop_zz写的很好;
http://blog.csdn.net/fop_zz/article/details/62418370
但是代码是我的好嘿嘿
但是Fop_zz还是有些事情没讲清楚的;
这个我就不画图了;
如果你打过bfs,你就会知道,更新到x层时,1~x-1都要跟新过;
换句话说,就是要搞一个类似拓扑一样的东西;
BFS在操作上优于dfs;
你看看这张图;
为什么E连到E;
因为HE是SHE的后缀;
看过代码的同学知道;
这个是最长后缀;
所以fail的含义,就是这个点到跟节点这么一个字符串上的最长后缀;
这就是KMP啊;
所以AC就是在trie上面跑KMP啊;
那我在解释一下关于查找;
如果你dfs到了一个点,那么你不仅要判断这个点是不是end;
你还要判断这个点的fail指针指向的点是不是end;
直到fail指向0或者指向的点已经被访问;
为什么?
因为fail指向当前字符串的最长后缀;
如果当前串被找到啦;
那么显然这个后缀也应该存在;
但是我们不一定会直接搜到这个后缀;
比如
1
3
c
b
abc
abc
建树就是
对于abc;
a,b,abc三个串都是其子串,都出现过;
但是我们直接搜,
我们先搜到a;
我们搜ab的时候不会直接去搜b;
但b的确是出现了;
这时b是ab的后缀;
b的前缀没有,就当作root;
然后我们搜到
abc了;
这个时候c与刚才的b同理;
是abc的后缀;
所以也要搜;
那么看代码把;
看完之后你就发现这个AC自动机只不过是和KMP差不多,利用后缀去优化数据结构节约时间而已;
更好的写法http://blog.csdn.net/largecub233/article/details/62887977
#include<iostream>
#include<cstdio>
#include<cstring>
#define Ll long long
using namespace std;
struct trie{
int nxt[26],E,fail;
void cle(){memset(nxt,0,sizeof nxt);E=0;fail=0;}
}T[500005];
int q[500005],l,r;
int n,ll,ans,m;
char c[51],s[1000005];//两个读入
void init(int m){
int o=0,x;
for(int i=0;i<m;i++){
x=c[i]-'a';
if(!T[o].nxt[x])T[o].nxt[x]=++ll;
o=T[o].nxt[x];
}
T[o].E++;
}
void makezyy(){
l=r=0;
for(int i=0;i<26;i++)if(T[0].nxt[i])q[++r]=T[0].nxt[i],T[q[r]].fail=0;
while(r>l){
int x=q[++l];
for(int i=0;i<26;i++)
if(T[x].nxt[i]){
q[++r]=T[x].nxt[i];
int y=T[x].fail;
while(y&&!T[y].nxt[i])y=T[y].fail;
T[q[r]].fail=T[y].nxt[i];
}
}
}
void find(int m){
int o=0,x,y;
for(int i=0;i<m;i++){
x=s[i]-'a';
while(o&&!T[o].nxt[x])o=T[o].fail;
y=o=T[o].nxt[x];
while(y&&T[y].E!=-1){//这里要-1,因为-1代表表示已经访问过,不然会有无用的访问
ans+=T[y].E;
T[y].E=-1;
y=T[y].fail;
}
}
}
int main()
{
scanf("%d",&m);
while(m--){
while(ll)T[ll--].cle();
ans=0; T[0].cle();
scanf("%d",&n);
while(n--){
scanf("%s",c);
init(strlen(c));
}
makezyy();
scanf("%s",s);
find(strlen(s));
printf("%d\n",ans);
}}