zoukankan      html  css  js  c++  java
  • [NOI2011]阿狸的打字机——AC自动机之fail树的利用

    Description

     阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机。打字机上只有28个按键,分别印有26个小写英文字母和'B'、'P'两个字母。

    经阿狸研究发现,这个打字机是这样工作的:

    l 输入小写字母,打字机的一个凹槽中会加入这个字母(这个字母加在凹槽的最后)。

    l 按一下印有'B'的按键,打字机凹槽中最后一个字母会消失。

    l 按一下印有'P'的按键,打字机会在纸上打印出凹槽中现有的所有字母并换行,但凹槽中的字母不会消失。

    例如,阿狸输入aPaPBbP,纸上被打印的字符如下:

    a

    aa

    ab

    我们把纸上打印出来的字符串从1开始顺序编号,一直到n。打字机有一个非常有趣的功能,在打字机中暗藏一个带数字的小键盘,在小键盘上输入两个数(x,y)(其中1≤x,y≤n),打字机会显示第x个打印的字符串在第y个打印的字符串中出现了多少次。

    阿狸发现了这个功能以后很兴奋,他想写个程序完成同样的功能,你能帮助他么?

    Input

     输入的第一行包含一个字符串,按阿狸的输入顺序给出所有阿狸输入的字符。

    第二行包含一个整数m,表示询问个数。

    接下来m行描述所有由小键盘输入的询问。其中第i行包含两个整数x, y,表示第i个询问为(x, y)。

    Input

     输入的第一行包含一个字符串,按阿狸的输入顺序给出所有阿狸输入的字符。

    第二行包含一个整数m,表示询问个数。

    接下来m行描述所有由小键盘输入的询问。其中第i行包含两个整数x, y,表示第i个询问为(x, y)。

    Output

     输出m行,其中第i行包含一个整数,表示第i个询问的答案。

    Sample Input

    aPaPBbP
    3
    1 2
    1 3
    2 3

    Sample Output

    2
    1
    0

    HINT

     1<=N<=10^5

    1<=M<=10^5
    输入总长<=10^5

    Solution

    是一道能让我们深刻理解AC自动机的好题。

    输入很有意思。不是直接给n个字符串,而是给了一个打字的序列。

    发现,如果暴力找所有的字符串的话,那么会n^2 爆炸

    B,P有点意思。而且还是可以累积的。

    所以,可以直接建出来一棵trie树。B就是返回father,打一个字母就是找一个son,P就是在这个点打标记。

    怎么找?

    暴力kmp匹配n^2

    我们有多个模式串,肯定要想一想AC自动机

    简化问题,假设一个主串T,多次询问一个S在T中出现次数?

    trie毕竟是一个前缀集合,而S在T的出现位置,只能是若干个T[1...i]前缀的后缀。

    如果我们走一遍T,把路径上点权++,代表这个点是一个T的前缀。

    怎么判断S在某个前缀中出现过?

    反过来,如果一个前缀T[1...i]中有S,那么从i不断跳fail指针,必然有一次会跳到S的结尾处。

    而,AC自动机的fail是一棵树,如果按照fail的方向,是一棵内向树。

    在fail树中,如果i是S结尾处的子树中的节点,那么意味着i跳fail可以到S结尾处。

    所以,可以用一个树状数组维护dfn序,就可以查询S结尾处子树内权值和。就是出现次数。

    如果主串T稍微动一下(末尾添加字符,删除末尾字符),也可以做到。直接把点权加上、删去即可。

    而这个主串变动其实是在trie上dfs

    每次dfs移动显然不划算。

    所以离线询问,挂在所有T的末尾。

    如果dfs到Ti的末尾,回答所有询问。即Sj的fail树子树的权值和。

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int N=100000+5;
    int n,m;
    int sum;
    struct node{
        int sub,key;
        int ans;
    }que[N];
    //trie&&AC machine
    int ch[N][26];
    bool has[N][26];
    int tot;
    int fail[N];
    int fa[N];
    vector<int>mem[N];
    vector<int>exi[N];
    int on[N];
    char s[N];
    void pre(){
        int now=0;
        fa[0]=0;
        for(int i=1;i<=n;i++){
            if(s[i]=='P'){
                sum++;
                on[sum]=now;
                exi[now].push_back(sum);
            }
            else if(s[i]=='B'){
                now=fa[now];
            }
            else{
                int x=s[i]-'a';
                if(!ch[now][x]) has[now][x]=1,ch[now][x]=++tot,fa[ch[now][x]]=now;
                now=ch[now][x];
            }
        }
    }
    //fail tree
    struct edge{
        int nxt,to;
    }e[2*N];
    int hd[N],cnt;
    void add(int x,int y){
        e[++cnt].nxt=hd[x];
        e[cnt].to=y;
        hd[x]=cnt;
    }
    int dfn[N],dfn2[N];
    int df;
    void dfs(int x,int fa){
        dfn[x]=++df;
        for(int i=hd[x];i;i=e[i].nxt){
            int y=e[i].to;
            if(y==fa)continue;
            dfs(y,x);
        }
        dfn2[x]=df;
    }
    void build(){
        queue<int>q;
        for(int i=0;i<=25;i++){
            if(has[0][i]) fail[ch[0][i]]=0,add(0,ch[0][i]),q.push(ch[0][i]);
        }
        while(!q.empty()){
            int x=q.front();q.pop();
            for(int i=0;i<=25;i++){
                if(has[x][i]){
                    fail[ch[x][i]]=ch[fail[x]][i];
                    add(ch[fail[x]][i],ch[x][i]);
                    q.push(ch[x][i]);
                }
                else{
                    ch[x][i]=ch[fail[x]][i];
                }
            }
        }
    }
    //array tree
    int f[N];
    void upda(int x,int c){
        for(;x<=n;x+=x&(-x)) f[x]+=c;
    }
    int query(int x){
        int ret=0;for(;x;x-=x&(-x)) ret+=f[x];return ret;
    }
    //ans
    void sol(int x,int fa){
        upda(dfn[x],1);
        for(int i=0;i<mem[x].size();i++){
            int son=on[que[mem[x][i]].sub];
            int kk=query(dfn2[son])-query(dfn[son]-1);
            que[mem[x][i]].ans=kk;
        }
        for(int i=0;i<=25;i++){
            if(has[x][i]){
                sol(ch[x][i],x);
            }
        }
        upda(dfn[x],-1);
    }
    int main(){
        scanf("%s",s+1);
        n=strlen(s+1);
        pre();
        build();
        dfs(0,0);//warning root is 0
        scanf("%d",&m);
        int x,y;
        for(int i=1;i<=m;i++){
            scanf("%d%d",&que[i].sub,&que[i].key);
            que[i].ans=0;
            mem[on[que[i].key]].push_back(i);
        }
        sol(0,0);
        for(int i=1;i<=m;i++){
            printf("%d
    ",que[i].ans);
        }
        return 0;
    }

    upda:2019.3.27:

    直接暴力fail树上线段树合并,就可以在线辣 

  • 相关阅读:
    nodejs实现端到端加密
    DiffieHellman(迪菲-赫尔曼)密钥交换算法原理及其实现
    MongoDB主备 版本3.6.5
    linux源地址转换
    Mac下IDEA快捷键操作
    Ubuntu离线安装gcc
    VM安装Mac时,使用Unlocker12 插件时报getTools错误的问题
    华为OSPF与ACL综合应用实例讲解
    基于全局地址池的DHCP
    浮动静态路由及负载均衡
  • 原文地址:https://www.cnblogs.com/Miracevin/p/9757584.html
Copyright © 2011-2022 走看看