BZOJ3881: [Coci2015]Divljak

本文详细介绍了AC自动机及Fail树的概念与应用。通过一个具体的题目实例,讲解了如何利用AC自动机进行字符串匹配,并结合Fail树解决特定问题的方法。

BZOJ3881: [Coci2015]Divljak

AC自动机·Fail树

题解:

一开始想错了,还是对AC自动机的性质太不熟悉了。。。

以前没接触过Fail树,先来简单总结一下。
Fail树就是只保留AC自动机的fail指针并将其反向建出来的树。
对于Fail树上的节点,它代表的字符串是其子树中的节点代表的字符串的后缀。也就是下面的包含上面的。

考虑这样一个问题:假设给你若干个字符串,每次询问a在b中出现了多少次?可以把字符串建AC自动机,建Fail树,让b在AC自动机上走,走到的点+1,最后查询一下a在Fail树上的子树和即可。处理下一个询问之前别忘了把+1的点清零。

这道题的不同在于,询问是a在几个b中出现过,就是说a在一个b中出现很多次也只能贡献1。因此可以让b在AC自动机上走,走到的点+1,然后把走到的点按dfs序排序,相邻两个的lca处-1。查询a的子树和即是答案。

Code:

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#define D(x) cout<<#x<<" = "<<x<<"  "
#define E cout<<endl
using namespace std;
const int N = 2000005;

int n,q; char str[N]; int tp[N];
int ch[N][26],fail[N],ptr[N],sz=1;
int pa[N][25],dep[N],pos[N],end[N],tim;

int insert(char s[]){
    int x=1;
    for(int i=0;s[i]!='\0';i++){
        if(!ch[x][s[i]-'a']){
            ch[x][s[i]-'a']=++sz;
        }
        x=ch[x][s[i]-'a'];
    }
    return x;
}

void acBuild(){
    queue<int> q; q.push(1); int f;
    while(!q.empty()){
        int u=q.front(); q.pop();
        for(int c=0;c<26;c++){
            if(ch[u][c]){
                int v=ch[u][c];
                for(f=fail[u];f && !ch[f][c];f=fail[f]);
                if(f) fail[v]=ch[f][c]; else fail[v]=1;
                q.push(v);
            }
            else{
                ch[u][c]=ch[fail[u]][c]?ch[fail[u]][c]:1;
            }
        }
    }
}

struct Edge{
    int to,next;
} e[N*2];
int head[N], ec;
void add(int a,int b){
//  D(a); D(b); E;
    ec++; e[ec].to=b; e[ec].next=head[a]; head[a]=ec;
}

void dfs(int u,int f){
    pa[u][0]=f; dep[u]=dep[f]+1; pos[u]=++tim;
    for(int i=head[u];i;i=e[i].next){
        int v=e[i].to;
        if(v==f) continue;
        dfs(v,u);
    }
    end[u]=tim;
}

void initLCA(){
    for(int j=1;j<=21;j++){
        for(int i=1;i<=sz;i++){
            pa[i][j]=pa[pa[i][j-1]][j-1];
        }
    }
}

int LCA(int a,int b){
    if(dep[a]<dep[b]) swap(a,b);
    int cha=dep[a]-dep[b];
    for(int j=21;j>=0;j--){
        if(cha&(1<<j)) a=pa[a][j];
    }
    if(a!=b){
        for(int j=21;j>=0;j--){
            if(pa[a][j]!=pa[b][j]){
                a=pa[a][j]; b=pa[b][j];
            }
        }
        a=pa[a][0];
    }
    return a;
}

void failBuild(){
    for(int i=2;i<=sz;i++) add(fail[i],i);
    dfs(1,0); initLCA();
//  for(int i=1;i<=sz;i++) D(i), D(pos[i]), E;
}

struct BIT{
    int c[N], sz;
    void init(int _sz){ sz=_sz; memset(c,0,sizeof(c)); }
    void add(int x,int d){ while(x<=sz){ c[x]+=d; x+=x&(-x); } }
    int sum(int x){ int ans=0; while(x){ans+=c[x];x-=x&(-x);} return ans; }
    int sum(int x,int y){ return sum(y)-sum(x-1); }
} bit;

bool cmp(int a,int b){
    return pos[a]<pos[b];
}

void work(char s[]){
    int x=1; int tpsz=0; tp[tpsz++]=1;
    for(int i=0;s[i]!='\0';i++){
        x=ch[x][s[i]-'a'];
        tp[tpsz++]=x;
    }
    sort(tp,tp+tpsz,cmp); 
    tpsz=unique(tp,tp+tpsz)-tp;
//  D(tpsz); E;
//  for(int i=0;i<tpsz;i++){ D(tp[i]); E; }
    for(int i=0;i<tpsz;i++){
        bit.add(pos[tp[i]],1);
        if(i) bit.add(pos[LCA(tp[i-1],tp[i])],-1);
    }
}

void solve(){
    bit.init(sz);
    cin>>q; int op,p;
    while(q--){
//      D(q); E;
        scanf("%d",&op);
        if(op==1){
            scanf("%s",str);
            work(str);
        }
        else{
            scanf("%d",&p);
            int l=pos[ptr[p]], r=end[ptr[p]];
//          D(l); D(r); E;
            printf("%d\n",bit.sum(l,r));
        }
    }
}

int main(){
    freopen("a.in","r",stdin);
    cin>>n;
    for(int i=1;i<=n;i++){
        scanf("%s",str);
        ptr[i]=insert(str);
    }
//  D(sz); E;
    acBuild();
//  for(int i=1;i<=sz;i++) D(i), D(fail[i]), E;
    failBuild();
    solve();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值