• [NOI2011][bzoj2434] 阿狸的打字机 [AC自动机+dfs序+fail树+树状数组]


    题面

    传送门

    正文

    最暴力的

    最暴力的方法:把所有询问代表的字符串跑一遍kmp然后输出

    稍微优化一下:把所有询问保存起来,把模板串相同的合并,求出next然后匹配

    但是这两种方法本质没有区别,都是暴力

    不那么暴力的

    我们对于所有的串建立一个AC自动机,把询问按照$y$排序,然后在AC自动机上面跑,每次跳fail更新答案

    这样可以拿到70分,但是时间上限还是会$Oleft(n^2 ight)$左右

    巧妙的优化

    这道题里面,所有的模板串和文本串都在AC自动机里

    那么,题目中实际是在要求什么呢?

    就是有多少个x串是y串的一个前缀的后缀

    那么,在AC自动机自己身上有没有满足这样的检索的结构呢?

    有的,那就是fail指针

    trie上的某一个前缀的fail指针,指向的是作为它的最长后缀的那个节点;同时,从某个前缀开始一路沿着fail指针跳,直到根节点,过程中所有的节点代表的前缀都是这个前缀的后缀

    也就是说,我们把fail指针看成树边,将这个“fail树”(不要和kmp的next树搞混了)提取出来,那么我们就可以把题目的询问变成这样:

    把代表y串的所有前缀的节点打上标记,那么代表x串的节点的子树中的标记个数,就是这个询问的答案

    维护个数和可以用fail树上的dfs序以及树状数组共同完成

    正解

    上述过程中有一个重复的地方:每次我们都需要把树状数组归零,然后重新把新的y串前缀节点插进去——即使我们使用把y排序的方法也会TLE

    但是这个过程中有一个问题:有些点会进进出出好多遍,并不高效,我们需要找到一个办法,使得每个AC自动机上的点只进出树状数组一次

    那么谁能满足这个要求呢?

    还是dfs序,只不过是原trie树上的dfs序

    我们把输入的询问按照y串在trie树上的dfs序排序,依次加入、删除

    因为按照dfs序遍历可以使每个点进入一次离开一次,所以这个方法的总时间效率只有$Oleft(nlogn ight)$

    这样这道题就做完了

    Code

    本题的映射非常多,而且很繁复,有很多重复意义的东西,调试的时候一定要小心

    变量名有点乱,还请见谅

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<vector>
    #define rank deep_dark_fantasy
    using namespace std;
    struct node{
        int fail,fa,son[26];
        vector<int>num;
        node(){fail=fa=0;memset(son,0,sizeof(son));num.clear();}
    }a[100010];int cnt,tot;
    int dfn[100010],clk,end[100010],tmplca,pre[100010],rank[100010];
    //dfn是trie树dfs序,rank是dfn的反映射
    //end是每个字符串在trie树上的节点编号
    //pre表示由dfs序为i的串向dfs序为i+1的串转移时的lca,tmplca是维护这个的辅助变量
    struct edge{
        int to,next;
    }e[100010];int cnte,first[100010];
    inline void addedge(int u,int v){
        e[++cnte]=(edge){v,first[u]};first[u]=cnte;
    }
    inline void add(char s[]){
        int len=strlen(s),cur=0,i;
        for(i=0;i<len;i++){
            if(s[i]=='P'){a[cur].num.push_back(++tot);continue;}
            if(s[i]=='B'){cur=a[cur].fa;continue;}
            if(!a[cur].son[s[i]-'a']) a[cur].son[s[i]-'a']=++cnt;
            a[a[cur].son[s[i]-'a']].fa=cur;cur=a[cur].son[s[i]-'a'];
        }
    }
    void getdfn(int u){
        int i,v,len=a[u].num.size();
        for(i=0;i<len;i++){
            dfn[++clk]=a[u].num[i];
            rank[a[u].num[i]]=clk;
            end[a[u].num[i]]=u;
            pre[clk]=tmplca;tmplca=u;
        }
        for(i=0;i<26;i++){
            v=a[u].son[i];if(!v) continue;
            getdfn(v);tmplca=u;
        }
    }
    int q[100010];
    void getfail(){
        int head=0,tail=0,i,u,v;
        for(i=0;i<26;i++){
            if(!a[0].son[i]) continue;
            a[a[0].son[i]].fail=0;q[tail++]=a[0].son[i];
        }
        while(head<tail){
            u=q[head++];
            for(i=0;i<26;i++){
                v=a[u].son[i];
                if(v) a[v].fail=a[a[u].fail].son[i],q[tail++]=v;
                else a[u].son[i]=a[a[u].fail].son[i];
            }
        }
        memset(first,-1,sizeof(first));
        for(i=1;i<=cnt;i++) addedge(a[i].fail,i);
    }
    char s[100010];int Q;
    struct query{
        int x,y,num,ans;
    }qq[100010];
    bool cmp(query l,query r){return rank[l.y]<rank[r.y];}
    bool cmp2(query l,query r){return l.num<r.num;}
    int now=0,tmpnow;
    struct tree{//树状数组
        int x[100010];
        tree(){memset(x,0,sizeof(x));}
        int lowbit(int pos){return pos&(-pos);}
        void change(int pos,int type){
            for(int i=pos;i<=cnt+1;i+=lowbit(i)) x[i]+=type;
        }
        int ask(int pos){
            int re=0;
            for(int i=pos;i>0;i-=lowbit(i)) re+=x[i];
            return re;
        }
    }T;
    int faildfn[100010],failclk=0,le[100010],ri[100010];
    //faildfn是fail树上的dfs序,le和ri是某个节点在树状数组上的左右区间
    void get_fail_dfn(int u){
        int i,v;faildfn[u]=++failclk;le[u]=failclk;
        for(i=first[u];~i;i=e[i].next){
            v=e[i].to;
            get_fail_dfn(v);
        }
        ri[u]=failclk;
    }
    int main(){
        scanf("%s",s);int i,j,x,y,xx;
        add(s);getdfn(0);
        getfail();get_fail_dfn(0);
        
        scanf("%d",&Q);
        for(i=1;i<=Q;i++) scanf("%d%d",&qq[i].x,&qq[i].y),qq[i].num=i;
        sort(qq+1,qq+Q+1,cmp);//排序
        
        j=1;
        for(i=1;i<=tot;i++){
            y=dfn[i];tmpnow=end[y];
            while(now!=pre[i]){
                T.change(faildfn[now],-1);now=a[now].fa;
            }
            while(tmpnow!=now){
                T.change(faildfn[tmpnow],1);tmpnow=a[tmpnow].fa;
            }//插入、删除节点
            now=end[y];
            while(qq[j].y==y){//处理询问
                xx=end[qq[j].x];
                qq[j].ans=T.ask(ri[xx])-T.ask(le[xx]-1);
                j++;
            }
        }
        
        sort(qq+1,qq+Q+1,cmp2);
        for(i=1;i<=Q;i++) printf("%d
    ",qq[i].ans);
    }
    
  • 相关阅读:
    [洛谷 U68862] 奶牛滑迷宫 题解
    STL的妙用(二)——洛谷 P2073 送花
    平衡树 x 01-trie √
    最小生成树(大纲,待补全)
    单源最短路算法
    黑科技:如何提高整数域内高斯消元的精度和速度——高斯消元与辗转相除法的结合
    Scratch的入门笔记
    Ubuntu18.04安装Tensorflow
    Ubuntu18.04安装英伟达显卡驱动
    macOS下appstore提示未能完成该操作的解决办法
  • 原文地址:https://www.cnblogs.com/dedicatus545/p/8907400.html
Copyright © 2020-2023  润新知