AC自动机-洛谷3121 [USACO15FEB]审查(黄金)Censoring (Gold)

https://www.luogu.org/problem/show?pid=3121#sub
首先题目看清楚

FJ注意到列表中的单词不会出现一个单词是另一个单词子串的情况,这意味着每个列表中的单词在S中出现的开始位置是互不相同的

这意味着你在找答案时,当一个节点正常访问的时候,你不用去寻找fail;
你只要在匹配失败的时候取fail就好了;
关于fail的意义,可以看我AC自动机的博客;
然后我们在造trie时,顺便把一个字符串的长度记录下来;
end=一个字符串的长度;
然后我们开2个栈;
a[]记录当前搜到那个点;
b[]记录当前搜那个char;
然后如果发现当前节点是end;
直接弹出栈顶end个元素;
更新now指针,继续搜;
好了;
有个事情要注意;
我原始的AC自动机;

void makezyy(){
    for(int i=0;i<26;i++)if(T[0].nxt[i])q[++r]=T[0].nxt[i];
    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;
    for(int i=0;i<m;i++){
        int x=s[i]-'a';
        int y=o;
        while(y&&!T[y].nxt[x])y=T[y].fail;
        o=T[y].nxt[x];
        a[++tot]=o;
        b[tot]=i;
        if(T[o].E){
            tot-=T[o].E;
            o=a[tot];
        }    
    }
}

大家看,这个好理解,但是用了while循环,会被卡一个点;
那我们看看更优的写法;

void makezyy(){
    for(int i=0;i<26;i++)if(T[0].nxt[i])q[++r]=T[0].nxt[i];
    while(r>l){
        int x=q[++l];
        for(int i=0;i<26;i++)
            if(!T[x].nxt[i])T[x].nxt[i]=T[T[x].fail].nxt[i];
            else{
                q[++r]=T[x].nxt[i];
                T[T[x].nxt[i]].fail=T[T[x].fail].nxt[i];
            }
    }
}
void find(int m){
    int o=0;
    for(int i=0;i<m;i++){
        o=T[o].nxt[s[i]-'a'];
        a[++tot]=o;
        b[tot]=i;
        if(T[o].E){
            tot-=T[o].E;
            o=a[tot];
        }   
    }
}

种方法巧妙地运用地推;
两种方法求出的fail值是相同的;
但是这种方法会无法辨别出某个节点在一开始有没有某个儿子;
因为makezyy之后空的儿子自动变成了fail;
好像还可以优化的吧;

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
using namespace std;
struct trie{
    int nxt[26],fail,E;
}T[100005];
int q[100005],l,r,a[100005],b[100005],tot;
int n,m,ll;
char s[100005],c[100005];
void init(int m){
    int o=0;
    for(int i=0;i<m;i++){
        int x=c[i]-'a';
        if(!T[o].nxt[x])T[o].nxt[x]=++ll;
        o=T[o].nxt[x];
    }T[o].E=m;
}
void makezyy(){
    for(int i=0;i<26;i++)if(T[0].nxt[i])q[++r]=T[0].nxt[i];
    while(r>l){
        int x=q[++l];
        for(int i=0;i<26;i++)
            if(!T[x].nxt[i])T[x].nxt[i]=T[T[x].fail].nxt[i];
            else{
                q[++r]=T[x].nxt[i];
                T[T[x].nxt[i]].fail=T[T[x].fail].nxt[i];
            }
    }
}
void find(int m){
    int o=0;
    for(int i=0;i<m;i++){
        o=T[o].nxt[s[i]-'a'];
        a[++tot]=o;
        b[tot]=i;
        if(T[o].E){
            tot-=T[o].E;
            o=a[tot];
        }   
    }
}
int main()
{
    scanf("%s",s);
    scanf("%d",&n);
    while(n--)scanf("%s",c),init(strlen(c));
    makezyy();
    find(strlen(s));
    for(int i=1;i<=tot;i++)cout<<s[b[i]];
}

转载于:https://www.cnblogs.com/largecube233/p/6797890.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值