bzoj2434 阿狸的打字机NOI2011ac自动机+fail树+树状数组+dfs序详解

题目戳这里:http://www.lydsy.com/JudgeOnline/problem.php?id=2434
这道题我们用ac自动机的fail指针思想来做。
首先它要求查询一个x串在y串出现了多少次
我们可以很快想到如果从y的一个节点走fail走到了x尾节点那么x在y中出现过一次。
那么我们逆向思考:
从y反着走fail可以走到有多少个节点在y这个串上,那么x就在y中出现了多少次。
那么我们可以建一颗fail树,把fail反向就是一颗fail树,它满足树的性质,因为一个点只有一个fail指针且在这个点的上层- -。
那么我们把y串的所有节点都变为1;
然后我们其实就要求,fail树中的x节点的子树中有多少个1,可以用dfs序加树状数组实现。

对于字符串的P和B操作,我们考虑一颗Trie树,那么P操作就是输出,也就是当前点变为一个串,B操作就是返回当前点的父亲节点。
那么我们每加入一个节点我们在树状数组上修改这个点+1,如果B操作也就是返回,我们把这个点删掉也就是在树状数组上-1,我们离线存储所有询问,对于相同的y询问,同时处理所有x,这样我们可以证明对于原字符串上的每个点只会被加入和删除1次。
所以复杂度nlogn
还是代码说明吧。。。。

n表示有多少个输出的串,pos【i】表示第i个被打印的串的尾节点编号,cha数组就是树状数组,insert函数是建立Trie树,maknxt是求出fail指针,然后建立fail树,跑一边dfs序,求in,out
然后就什么了。。。

#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
#include<vector>
#include<cstdio>
#define N 100005
using namespace std;
char s[N];
struct Edge{
    int v,next,w;
};
Edge e[2*N];
int stack[N],head[N],num,top,in[N],out[N],cnt,len,nxt[N][30],fail[N],pos[N],n,cha[N],idc,m;
int aa,bb,ans[N];
void adde(int i,int j,int w){
    e[++num].v=j;
    e[num].next=head[i];
    e[num].w=w;
    head[i]=num;
}
void insert(){
    top=0;
    stack[++top]=0;
    for(int i=1;i<=len;i++){
        if(s[i]=='P')
            pos[++n]=stack[top];
        else if(s[i]=='B')
            top--;
        else{
            int x=s[i]-'a',&k=nxt[stack[top]][x];
            stack[++top]=k?k:k=++cnt;
        }
    }
}
void maknxt(){
    queue<int>q;
    q.push(0);
    while(!q.empty()){
        int u=q.front();q.pop();
        for(int i=0;i<26;i++){
            int v=nxt[u][i];
            if(v==0)
                nxt[u][i]=nxt[fail[u]][i];
            else
                q.push(v);
            if(u&&v)
                fail[v]=nxt[fail[u]][i];
        }
    }
}
void dfs(int u){
    in[u]=++idc;
    for(int i=head[u];i;i=e[i].next){
        int v=e[i].v;
        dfs(v);
    }
    out[u]=idc;
}
void add(int x,int y){
    for(int i=x;i<=idc;i+=i&-i)
    cha[i]+=y;
}
int qqq(int x){
    int rt=0;
    for(int i=x;i;i-=i&-i)
    rt+=cha[i];
    return rt;
}
int query(int l,int r){
    return qqq(r)-qqq(l-1);
}
void solv(){
    top=0;stack[++top]=0;
    int sum=0;
    for(int i=1;i<=len;i++){
        if(s[i]=='P'){
            ++sum;
            for(int i=head[sum];i;i=e[i].next){
                int v=e[i].v,id=e[i].w;
                ans[id]=query(in[pos[v]],out[pos[v]]);
            }
        }else if(s[i]=='B'){
            add(in[stack[top--]],-1);
        }
        else {
            int x=s[i]-'a',pp=nxt[stack[top]][x];
            stack[++top]=pp;
            add(in[pp],1);
        }
    }
}
int main(){
    scanf("%s",s+1);len=strlen(s+1);
    insert();
    maknxt();
    for(int i=1;i<=cnt;i++)
    adde(fail[i],i,0);
    dfs(0);
    num=0;memset(head,0,sizeof(head));
    scanf("%d",&m);
    for(int i=1;i<=m;i++){
        scanf("%d%d",&aa,&bb);
        adde(bb,aa,i);
    }
    solv();
    for(int i=1;i<=m;i++)
    printf("%d\n",ans[i]);
}

反思:

这道题我开始一直wa,原来是我求fail求错了,少打了一行语句,要多理解模板啊。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值