zoukankan      html  css  js  c++  java
  • 【BZOJ2434】[NOI2011]阿狸的打字机 AC自动机+DFS序+树状数组

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

    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)。

    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

    题解:先构建fail树,然后对于一个询问a b,我们就可以将其转化成 在以a为根的子树(这里的子树指fail树)中,有多少个节点在单词b中出现过,于是我们自然想到要用离线处理来搞。就是在DFS遍历fail树的时候,分别记录 从上面查询到该点 和 从下面回溯到该点 时,有多少个节点在b中出现过,两者的差就是我们要的答案,我们只需要把 询问 以 链表 的形式存储到a上就可以了。

    但我们发现一个问题,对于某一个节点,它可能存在于很多个单词之中,那我们搜到这个点时就要把这些单词出现的次数全部+1,怎么办?

    我们不妨分析一下AC自动机的性质,对于所有包含节点x的单词,他们的结束点一定在以x为根的子树中(这里的子树指AC自动机)。仔细观察发现,这些结束点在AC自动机的DFS序中一定是一段连续的区间(中间没有其它结束点)。利用这个性质,我们就可以对于每一个节点x,求出它对应的区间,然后将整个区间+1,这个可以用树状数组实现。

    (以上都是个人想法,也许麻烦了,但事实证明可以AC)

    这么做的恶心之处就在于:1.对于一个结束点,既要知道它在AC自动机中的的编号,又要知道它在询问中的编号,还要知道它在DFS序中的编号,特别容易搞混。2.DFS要进行两遍,一遍在AC自动机上搞,一遍在fail树上搞。3.我们一开始求出来的fail树是从儿子指向父亲的,我们还要正向再建一遍树。4.一个结束点可能对应多个单词。

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <queue>
    using namespace std;
    const int maxn=100010;
    struct node
    {
        int ch[26],fail,l,r,cnt;
    }p[maxn];
    char str[maxn];
    int sta[maxn],tp,s[maxn],pos[maxn];
    int n,tot,len,sum,now;
    int t1[maxn],n1[maxn],h1[maxn],ans[maxn],n2[maxn],h2[maxn];
    int tree[maxn];
    queue<int> q;
    void updata(int x,int v)
    {
        for(int i=x;i<=sum;i+=i&-i)    tree[i]+=v;
    }
    int query(int x)
    {
        int i,ret=0;
        for(i=x;i;i-=i&-i)    ret+=tree[i];
        return ret;
    }
    void dfs1(int x)    //遍历AC自动机 
    {
        p[x].l=p[x].r=now;    //如果这个点是结束点,那么now就是该点在DFS序中的顺序
        now+=p[x].cnt;    //[l,r]就是这个点所影响的单词的区间
        for(int i=0;i<26;i++)
        {
            if(p[x].ch[i])
            {
                dfs1(p[x].ch[i]);
                p[x].r=max(p[x].r,p[p[x].ch[i]].r);
            }
        }
    }
    void build()
    {
        int i,j,u,t;
        q.push(1);
        while(!q.empty())
        {
            u=q.front(),q.pop();
            for(i=0;i<26;i++)    //这里不要用fail修改儿子了,因为还要DFS 
            {
                if(!p[u].ch[i])    continue;
                q.push(p[u].ch[i]);
                t=p[u].fail;
                while(!p[t].ch[i]&&t)    t=p[t].fail;
                if(t)    p[p[u].ch[i]].fail=p[t].ch[i];
                else    p[p[u].ch[i]].fail=1;
            }
        }
    }
    void dfs(int x)    //遍历fail树 
    {
        int i;
        for(i=h1[x];i;i=n1[i])
        {
            ans[i]-=query(p[t1[i]].l);
        }
        updata(p[x].l,1);
        updata(p[x].r+1,-1);
        for(i=h2[x];i;i=n2[i])
        {
            dfs(i);
        }
        for(i=h1[x];i;i=n1[i])    //两次差值就是答案,意会一下 
        {
            ans[i]+=query(p[t1[i]].l);
        }
    }
    int main()
    {
        scanf("%s%d",str,&n);
        len=strlen(str);
        int i,j,k,u,t,a,b;
        u=sta[++tp]=1;
        tot=1;
        for(i=0;i<len;i++)    //用栈搞一搞,输入其实很简单 
        {
            if(str[i]=='B')    u=sta[--tp];
            else if(str[i]=='P')    p[u].cnt++,pos[++sum]=u;
            else
            {
                if(!p[u].ch[str[i]-'a'])    p[u].ch[str[i]-'a']=++tot;
                u=p[u].ch[str[i]-'a'];
                sta[++tp]=u;
            }
        }
        for(i=1;i<=n;i++)
        {
            scanf("%d%d",&a,&b);    //将询问存储到a节点上 
            t1[i]=pos[b];
            n1[i]=h1[pos[a]];
            h1[pos[a]]=i;
        }
        build();
        now=1;
        dfs1(1);
        for(i=2;i<=tot;i++)    //重建fail树 
        {
            n2[i]=h2[p[i].fail];
            h2[p[i].fail]=i;
        }
        dfs(1);
        for(i=1;i<=n;i++)    printf("%d
    ",ans[i]);
        return 0;
    }
  • 相关阅读:
    【Vue】 修饰符sync
    【VUE】vue路由跳转的方式
    【Element】elementui的Cascader 级联选择器,在懒加载的时候数据无法回显的解决方案
    【ES6】利用ES6 Set 将数组去重
    【.NETCORE】Refit 框架
    【.NETCORE】ASP.NET Core SignalR
    【Visual Studio Code】驼峰翻译助手
    VueX(Vue状态管理模式)
    hdmi 随笔
    ad 差分布线 等长布线
  • 原文地址:https://www.cnblogs.com/CQzhangyu/p/6262165.html
Copyright © 2011-2022 走看看