AC自动机

使用场景

  • 在现实生活中有这样一个问题,如何把一篇文章中禁用的句子或单词(例如某些脏话)删去。如果把库(我们把禁用的句子或单词造成一个库)中的 每个串都和待配串(文章)进行判断子串的操作的话,效率也太低了。

基本流程

<1> build_trie

void build_tri(int x){//trie树:单词查找树
    scanf("%s",str);
    int cur=0;
    for(int i=0;str[i];i++){
        int t=str[i]-'a';
        if(!ch[cur][t])ch[cur][t]=++num;
        cur=ch[cur][t];
    }to[x]=cur;//表示第i个匹配串的终止点也是fail树上的点 ,于是我们通过to[i] 就可以"接入"fail树了 
}

<2> build_AC

void build_AC(){//我们利用Bfs来造树 
    int l=0,r=0;
    for(int i=0;i<26;i++)if(ch[0][i])Q[r++]=ch[0][i];
    while(l<r){
        int x=Q[l++];
        for(int i=0;i<26;i++){
            if(ch[x][i]) Q[r++]=ch[x][i],fail[ch[x][i]]=ch[fail[x]][i]; 
            else ch[x][i]=ch[fail[x]][i];//用fail填空儿子 
        }   
    }
}

<3> build_fail

for(int i=0;i<=num;i++)Add(fail[i],i);//建fail树由fail[i]指向i

<4> pre_operation
<5> run_word & update

void run_word(){
    scanf("%s",str);
    for(int i=0,cur=0;str[i];i++)
        cur=ch[cur][str[i]-'a'];
        //可以在过程中更新收集节点的信息 
}

例题

  • 我们先给出一个匹配串库(包含n个串)
  • 有m个操作:
    • 添加一个待配串
    • 询问第t个匹配串被多少个待配串包含
  • 暴力
    • 每次更新都把串一个一个扫过来,输出ans[to[t]]即可
void update(){
    int cur=0,r=0;
    for(int i=0;str[i];i++){
        int k=cur=ch[cur][str[i]-'a'];
        while(k&&!mark[k]){
            Q[r++]=k;
            mark[k]=1;
            k=fail[k];
        }
    }for(int i=0;i<r;i++)mark[Q[i]]=0,++ans[Q[i]];
}
  • 思考
    • 对于每一个待配串,我们在读入它的时候就把它在tire树上跑一遍,每当一个匹配串被匹配成功时就把它的val加一。
    • 可惜如果出现了一个待配串中包含多个相同的匹配串时就不行了
    • 此时我们可以运用类似七彩树的方法来解这题。
  • 分析
    • 建fail树,(fail树的父节点权等于其子树权的和加原本的权)
    • 每次跑一下待配串,匹配成功后会个每个匹配成功的点的权加一,而子节点被增加时其父节点都增加了,应当减去
    • 于是我们可以把所有要增加的点储存下来,按dfn排序让相邻两者的lca减1,自身加1,这显然可以利用树状数组维护
/*
    由于一些匹配串可以在带配串上出现多次,例如有一个待配串abdabdc和一个匹配串abd我们发现 
    abd在fail树上就 被加了两次了,于是我们在LCA上减掉他们 
    我们按照dfn来排序得到的节点一定保证每个相邻节点的LCA深度最深 
*/
bool cmp(int x,int y){return L[x]<L[y];}
void run_word(){
    scanf("%s",str);
    int k=0;
    for(int i=0,cur=0;str[i];i++){
        cur=ch[cur][str[i]-'a'];
        s[k++]=cur;
    }
    sort(s,s+k,cmp);
    for(int i=0;i<k;i++){
        add(L[s[i]],1);
        if(i)add(L[LCA(s[i],s[i-1])],-1);
    }
}
void solve(){
    while(m--){
        scanf("%d",&t);
        if(t==1)run_word();
        else{
            scanf("%d",&t);
            t=to[t];//我们找到要查询的串 
            printf("%d\n",sum(R[t])-sum(L[t]-1));
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值