【LuoguP2414】[NOI2011]阿狸的打字机

题目链接

题意

先给你一个空字符串,每次将该字符串打印一份或者在它的末尾添加或删除一个字符

询问第x次答应的字符串在第y次中出现了多少次

Sol

先想到一个显然的暴力,把每次的字符串暴力存下来,然后看一下询问了哪一些模式串,用KMP算法求出next数组后去匹配

但是这太慢了,于是我们考虑用其他字符串数据结构,既然有这么多串,那么先来看看trie或AC自动机(一个东东啦)行不行

我们先来分析一下空间,发现可能很多串都有公共的前缀,每次加一个字符只会增加trie上的一个点,也就是说最后节点数也不会超过1e5,是开得下的.

然后再来看怎么做,一开始觉得是不同的一些模式串去匹配一些不同的文本串,不是很好做
考虑到每次询问的文本串也会被挂在trie上,找一下思路

我们有一个重要的东西:子串等于前缀的后缀!!!!!

于是我们发现一个串T是另一个串S的子串,当且仅当T是S某一个前缀的后缀,并且有多少个前缀满足该条件,T就在S中出现了多少次

我们突然意识到我们求解fail指针的时候就是在干这个事情,求解每一个前缀的最长的后缀等于另一个串的前缀,我们只需要强制只在fail指针指的是某个串的结尾时计算答案就可以了
这样的话我们是对于每一个给的y串,去找它所有前缀能用fail指向串尾的x串

于是我们现在的做法是,记录每一个串在AC自动机上的终止节点,求出fail指针
对于每一次询问,从AC自动机的根上下来,到一个点之后,不停的跳fail指针,把是串尾且包含这个询问的答案更新

但是这样每一次询问都要暴力跳,还是会TLE,怎么办呢?
考虑怎么才能对询问进行批量处理

对于每一次,询问我们是暴力跳fail指针,去找x串的结尾,发现每一个点有且仅有一个fail指针,应该是可以优化这个过程的,如果我们把fail指针理解为父亲指针的话,那么这就成了在一个x串结尾点的子树里面找是询问的点并把他的答案更新

我们构建出一颗fail树,容易发现祖先节点都是该点的后缀
其实并不能对询问进行批量处理,但是我们发现我们简化了求解询问的过程

我们还是单个看询问,那么就只要求子树内标记的点(y的前缀的点)的个数,即为x串的出现次数了,这个东西很好做,求出fail树的dfs序,那么每一个点的子树dfs序连续,用个树状数组维护一下就行了

于是我们的最终做法:在trie上便历一个串的所有前缀,并在fail树上标记,那么一个询问串在该串上的匹配次数就是他的子树(它是子树内点的后缀,一定满足是给的串的子串)内标记过的点的个数了,用BIT求出即可

至于为什么不会重复,因为相同的前缀我们只会标记一个,然后我们是对于一个询问串时,我们只有当它是某个前缀的后缀时才有贡献,这样保证了询问串的末尾位置在原串中不会重复,当然答案就不会算重啦(其实上面已经说过了)

P.S.:打字的过程就是在trie上跳上跳下,不要每次重新从根出发下来,直接模仿打字的过程存储和处理询问就可以了(不然你会T得很惨)

对字符串模式匹配和AC自动机的理解加深了不少….

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#include<cstdlib>
#include<vector>
using namespace std;
const int N=1e5+10;
inline int read(){
    register int x=0,t=1;char ch=getchar();
    for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') t=-1;
    for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+(ch-48);
    return x*t;
}
namespace AC{
    char S[N];int len;
    int next[26][N];int cnt=1;
    int fail[N];int fa[N];
    typedef pair<int,int> Pr;
    int end[N];vector<Pr> V[N];
    int n;
    struct edge{int to,next;}a[N];
    int head[N];int cur;
    void init(){
        scanf("%s",S+1);len=strlen(S+1);n=0;
        register int p=1;
        for(register int i=1;i<=len;++i){
            if(S[i]>='a'&&S[i]<='z') {
                register int k=S[i]-'a';
                if(!next[k][p]){next[k][p]=++cnt;fa[cnt]=p;p=cnt;}
                else p=next[k][p];
            }
            else if(S[i]=='B') p=fa[p];
            else end[++n]=cnt;
        }
    }
    queue<int> Q;
    void add(int x,int y){a[++cur]=(edge){y,head[x]};head[x]=cur;}
    void make_fail(){
        Q.push(1);
        fail[1]=1;
        while(!Q.empty()){
            register int p=Q.front();Q.pop();
            for(register int j=0;j<26;++j){
                if(next[j][p]){
                    if(p==1) fail[next[j][p]]=1;
                    else{
                        register int q=fail[p];
                        while(q!=1&&!next[j][q]) q=fail[q];
                        if(next[j][q]) fail[next[j][p]]=next[j][q];
                        else fail[next[j][p]]=1;
                    }
                    add(fail[next[j][p]],next[j][p]);
                    Q.push(next[j][p]);
                }
            }
        }
        return;
    }
    int subend[N];int I=0;int dfn[N];
    void dfs(int u){
        dfn[u]=++I;
        for(register int v,i=head[u];i;i=a[i].next){v=a[i].to;dfs(v);}
        subend[u]=I;
    }
    int tr[N];int ans[N];
#define lowbit(a) ((a)&(-a))

    void update(int p,int x){while(p<=I) tr[p]+=x,p+=lowbit(p);return;}
    inline int query(int p){
        register int res=0;
        while(p) res+=tr[p],p-=lowbit(p);
        return res;
    }
    void work(){
        make_fail();dfs(1);
        register int m;m=read();
        for(register int i=1;i<=m;++i){
            register int x=read(),y=read();
            V[end[y]].push_back(Pr(i,end[x]));
        }
        n=0;register int p=1;
        for(register int i=1;i<=len;++i){
            if(S[i]>='a'&&S[i]<='z') p=next[S[i]-'a'][p],update(dfn[p],1);
            else if(S[i]=='P'){
                ++n;
                register int ss=V[end[n]].size();
                for(register int i=0;i<ss;++i){
                    register Pr P=V[end[n]][i];
                    ans[P.first]=query(subend[P.second])-query(dfn[P.second]-1);
                }
            }
            else if(S[i]=='B') update(dfn[p],-1),p=fa[p];
        }
        for(register int i=1;i<=m;++i) printf("%d\n",ans[i]);
    }
}
int main()
{
    AC::init();AC::work();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值