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);
}}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值