2434: [Noi2011]阿狸的打字机

题目链接

题目大意:初始字串为空,首先给定一系列操作序列,有三种操作:
1.在结尾加一个字符
2.在结尾删除一个字符
3.打印当前字串
然后多次询问第x个打印的字串在第y个打印的字串中出现了几次

题解:建出Trie……
添加一个字符->新建一个子节点(若存在在不用新建),进入该子节点
删除一个字符->返回到父亲节点
打印当前字串->在当前节点标记是第几个打印串

跑出fail指针

对于AC自动机上的两个子串u,v(AC自动机上的子串x可以看成是从根节点到节点x连成的一个字符串),u是v的子串等价于u是v某个前缀的后缀,而判断u是x的后缀只需要看x能否沿着fail指针走到u

所以查询u在v中出现的次数即为查询根结点到v的路径中有多少个x,能沿着fail指针走到u

fail指针没有环 每个节点只有一个出度 那么反向之后显然是一棵树,x沿着反向fail边走到的点构成x的子树

那么查询(u,v)时,把root->v的路径上的点赋值为1,答案可以通过查询x的子树和解决,dfs序上单点修改区间求和就可以了

但是不能直接暴力改……需要采用一些方法减少修改次数
离线,按照v排序,这样就可以根据原来建立AC自动机的顺序进行修改了…………只需要进入时+1,离开时-1,查询时自然root->v的路径上的每一个点都是1了

我的收获:AC自动机~~

#include <bits/stdc++.h>
using namespace std;

const int N=100015;

int m;
int t,head[N];
int tim,in[N],out[N];
int cnt,pos[N],ans[N];
char s[N];

vector<int> G[N];

struct ques{int x,y,id;}a[N];
bool cmp(ques a,ques b){return a.y<b.y;}

struct edge{int to,nex;}e[N];
void add(int u,int v){e[++t].to=v,e[t].nex=head[u],head[u]=t;}

struct BIT{
    int c[N];
    void updata(int x,int v){for(;x<N;x+=x&(-x)) c[x]+=v;}
    int query(int x){int ret=0;for(;x>0;x-=x&(-x)) ret+=c[x];return ret;}
}Tree;

struct AC_DFA{ 

    #define idx s[i]-'a'

    int tot,c[N][26],fail[N],fa[N],q[N],l,r;

    void insert(){
        int x=0,len=strlen(s);
        for(int i=0;i<len;i++){
            if(s[i]=='P') pos[++cnt]=x;
            else if(s[i]=='B') x=fa[x];
            else{
                if(!c[x][idx]) c[x][idx]=++tot,fa[tot]=x;
                x=c[x][idx];
            }
        }
    }

    void getfail(){  
        l=0,q[r=1]=0,fail[0]=-1;
        while(l!=r){  
            int x=q[++l];  
            for(int i=0;i<26;i++)  
                if(c[x][i]) q[++r]=c[x][i],fail[c[x][i]]=!x?0:c[fail[x]][i];  
                else c[x][i]=!x?0:c[fail[x]][i];   
        }  
        for(int i=1;i<=tot;i++) G[fail[i]].push_back(i);
    }

    void dfs(int x){
        in[x]=++tim;
        for(int i=0;i<G[x].size();i++) dfs(G[x][i]);
        out[x]=tim;
    }

    void solve(){
        int x=0,cnt=0,k=1,len=strlen(s);
        for(int i=0;i<len;i++){
            if(s[i]=='P'){
                for(cnt++;a[k].y==cnt&&k<=m;k++){
                    int tmp=pos[a[k].x];
                    ans[a[k].id]=Tree.query(out[tmp])-Tree.query(in[tmp]-1);
                }
            }
            else if(s[i]=='B') Tree.updata(in[x],-1),x=fa[x];
            else x=c[x][idx],Tree.updata(in[x],1);
        }
    }

}AC;

void work()
{
    AC.solve();
    for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
}

void init()
{
    scanf("%s%d",s,&m);
    AC.insert();AC.getfail();AC.dfs(0);
    memset(head,-1,sizeof(head));
    for(int i=1;i<=m;i++) scanf("%d%d",&a[i].x,&a[i].y),a[i].id=i;
    sort(a+1,a+1+m,cmp);
}

int main()
{
    init();
    work();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值