zoukankan      html  css  js  c++  java
  • 【AC自动机】【树状数组】【dfs序】洛谷 P2414 [NOI2011]阿狸的打字机 题解

         这一题是对AC自动机的充分理解和树dfs序的巧妙运用。

    题目背景

    阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机。

    题目描述

    打字机上只有28个按键,分别印有26个小写英文字母和'B'、'P'两个字母。经阿狸研究发现,这个打字机是这样工作的:

    • 输入小写字母,打字机的一个凹槽中会加入这个字母(这个字母加在凹槽的最后)。
    • 按一下印有'B'的按键,打字机凹槽中最后一个字母会消失。
    • 按一下印有'P'的按键,打字机会在纸上打印出凹槽中现有的所有字母并换行,但凹槽中的字母不会消失。

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

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

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

    输入输出格式

    输入格式:

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

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

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

    输出格式:

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

    输入输出样例

    输入样例#1:
    aPaPBbP
    3
    1 2
    1 3
    2 3
    输出样例#1:
    2
    1
    0

    说明

    题解:

        AC自动机学习笔记中,我提到了fail树的每一条链上的点代表的字母都是一样的。实际上,根据fail和nxt的定义,在这条链上深度较深的点总包含深度较浅的点。更确切地说,深度较浅的点总是深度较深的点的后缀。

        有了上面的结论,我们就有了一种朴素算法:建立AC自动机,读取询问时让主串向父亲跳来检验fail指针指向的节点是不是模式串的结尾。接着沿fail指针检验一趟看是不是模式串的结尾,比KMP还要暴力一点。换句话说,就是个用AC自动机实现的KMP,而且代码巨短

    Code 40pts:

    #include<cstdio>
    #include<cstring>
    struct node
    {
        node *fa,*ch[26];
        node *fail;
        node(node *fa)
        {
            this->fa=fa;
            memset(ch,0,sizeof(ch));
            fail=NULL;
        }
        node()
        {
            fa=NULL;
            fail=NULL;
            memset(ch,0,sizeof(ch));
        }
    }*root=new node(),*End[100100];
    char s[100100];
    int cnt=0;
    node *q[100100];
    int l=0,r=0;
    void Fail()
    {
        root->fail=root;
        for(int i=0;i<26;++i)
            if(root->ch[i])
            {
                root->ch[i]->fail=root;
                q[++r]=root->ch[i];
            }
            else
                root->ch[i]=root;
        while(l<r)
        {
            node *p=q[++l];
            for(int i=0;i<26;++i)
                if(p->ch[i])
                {
                    p->ch[i]->fail=p->fail->ch[i];
                    q[++r]=p->ch[i];
                }
                else
                    p->ch[i]=p->fail->ch[i];
        }
        return;
    }
    int main()
    {
        scanf("%s",s);
        int l=strlen(s);
        node *now=root;
        for(int i=0;i<l;++i)
            if(s[i]=='P')
                End[++cnt]=now;
            else if(s[i]=='B')
                now=now->fa;
            else
            {
                if(!now->ch[s[i]-'a'])
                    now->ch[s[i]-'a']=new node(now);
                now=now->ch[s[i]-'a'];
            }
        Fail();
        int u,v,n;
        scanf("%d",&n);
        while(n--)
        {
            scanf("%d%d",&u,&v);
            node *now=End[v];
            int sum=0;
            while(now!=root)
            {
                node *p=now;
                while(p!=root)
                {
                    if(p==End[u])
                        ++sum;
                    p=p->fail;
                }
                now=now->fa;
            }
            printf("%d
    ",sum);
        }
        return 0;
    }

        发现上面这种做法需要递归fail指针,我们可以考虑构建fail树,设主串的结束节点为(a),模式串的结束节点为(b),那么看fail树中(b)的子树中有多少个节点在主串上。(注意这里不是主串结尾,在主串中间也可以)因为子树上的点通过fail边总可以连接到(b)这个节点上来,因此整棵子树都可以对(b)做出贡献。可以稍微优化一点时间复杂度,但是得分还是在40到50左右。

        在字典树上,有一种肥肠玄妙的优化叫树状数组。我们在这个题中只需要对字典树进行一遍DFS就可以了,在DFS过程中就可以处理各种询问,有点像tarjan求LCA算法的步骤。

        首先我们明确fail树和字典树上的节点是一一对应的。但是fail树需要一个dfs序,这里不要和节点编号或者节点指针搞混了;先对fail树进行dfs,存下每个点的编号(dfn)和子树大小(sz),方便之后对子树进行统计。

        接着对字典树进行dfs,遇到一个节点就把这个节点的dfn在树状数组中+1,表明这个节点在当前访问的串中。当访问到一个作为结束点的节点时,就处理它作为主串的询问。此时有了上面一点点优化(可达50分)的思想,直接查询每个询问的模式串结束节点的子树中有多少个被+1了,因为是子树,在dfs序上是连续的,所以树状数组求区间和就可以了。回溯时不要忘了把这个点的dfn在树状数组中-1,表示不属于接下来访问的串。

        这个题写起来真爽——虽然调的也很爽

    Code:

    #include<cstdio>
    #include<cstring>
    #include<vector>
    using std::vector;
    struct node//AC自动机
    {
        node *ch[26],*fail,*fa;
        vector<int> End;//是哪些字符串的结束点
        int sz,num;//dfs序用的子树大小和自己的编号(dfn)
        int head,ahead;
        bool exi[26];
        node(node *fa)
        {
            memset(exi,0,sizeof(exi));
            this->fa=fa;
            head=-1;
            ahead=-1;
            fail=NULL;
            memset(ch,0,sizeof(ch));
        }
        node()
        {
            memset(exi,0,sizeof(exi));
            head=-1;
            ahead=-1;
            fa=NULL;
            fail=NULL;
            memset(ch,0,sizeof(ch));
        }
    }*root=new node(),*End[100100];
    //存fail树的边
    struct edge
    {
        node *n;
        int nxt,num;
        edge(node *n,int nxt,int num)
        {
            this->n=n;
            this->nxt=nxt;
            this->num=num;
        }
        edge(node *n,int nxt)
        {
            this->n=n;
            this->nxt=nxt;
        }
        edge(){}
    }e[201000],ask[101000];
    int ecnt=-1,acnt=-1;
    void add(node *from,node *to)
    {
        e[++ecnt]=edge(to,from->head);
        from->head=ecnt;
        e[++ecnt]=edge(from,to->head);
        to->head=ecnt;
    }
    void Add(node *from,node *to,int num)
    {
        ask[++acnt]=edge(to,from->ahead,num);
        from->ahead=acnt;
    }
    //树状数组
    int c[100100];
    int lowbit(int x)
    {
        return x&(-x);
    }
    int dcnt=0;
    void change(int x,int v)
    {
        while(x<=dcnt)
        {
            c[x]+=v;
            x+=lowbit(x);
        }
        return;
    }
    int sum(int x)
    {
        int ans=0;
        while(x)
        {
            ans+=c[x];
            x-=lowbit(x);
        }
        return ans;
    }
    //求Fail
    node *q[100100];
    int l=0,r=0;
    void Fail()
    {
        root->fail=root;
        for(int i=0;i<26;++i)
            if(root->ch[i])
            {
                root->ch[i]->fail=root;
                add(root,root->ch[i]);
                q[++r]=root->ch[i];
            }
            else
                root->ch[i]=root;
        while(l<r)
        {
            node *p=q[++l];
            for(int i=0;i<26;++i)
                if(p->ch[i])
                {
                    p->ch[i]->fail=p->fail->ch[i];
                    add(p->ch[i],p->fail->ch[i]);
                    q[++r]=p->ch[i];
                }
                else
                    p->ch[i]=p->fail->ch[i];
        }
        return;
    }
    //dfs求dfs序
    void dfs(node *x,node *from)
    {
        x->sz=1;
        x->num=++dcnt;
        for(int i=x->head;~i;i=e[i].nxt)
            if(e[i].n!=from)
            {
                dfs(e[i].n,x);
                x->sz+=e[i].n->sz;
            }
        return;
    }
    //dfs求答案
    int ans[100100];
    void Dfs(node *x)
    {
        change(x->num,1);
        for(int i=x->ahead;~i;i=ask[i].nxt)
        {
            node *p=ask[i].n;
            ans[ask[i].num]=sum(p->num+p->sz-1)-sum(p->num-1);
        }
        for(int i=0;i<26;++i)
            if(x->exi[i])
                Dfs(x->ch[i]);
        change(x->num,-1);
    }
    char s[101000];
    int main()
    {
        scanf("%s",s);
        node *now=root;
        int cnt=0;
        for(int i=0;s[i]!='';++i)
            if(s[i]=='P')
            {
                now->End.push_back(++cnt);
                End[cnt]=now;
            }
            else if(s[i]=='B')
                now=now->fa;
            else
            {
                if(!now->ch[s[i]-'a'])
                    now->ch[s[i]-'a']=new node(now);
                now->exi[s[i]-'a']=1;
                now=now->ch[s[i]-'a'];
            }
        Fail();
        dfs(root,root);
        int n,u,v;
        scanf("%d",&n);
        for(int i=1;i<=n;++i)
        {
            scanf("%d%d",&u,&v);
            Add(End[v],End[u],i);
        }
        Dfs(root);
        for(int i=1;i<=n;++i)
            printf("%d
    ",ans[i]);
        return 0;
    }
    

      

  • 相关阅读:
    hive与hbase整合
    待重写
    hive DML
    【知识强化】第六章 总线 6.1 总线概述
    【知识强化】第五章 中央处理器 5.1 CPU的功能和基本结构
    【知识强化】第四章 指令系统 4.3 CISC和RISC的基本概念
    【知识强化】第四章 指令系统 4.2 指令寻址方式
    【知识强化】第四章 指令系统 4.1 指令格式
    【知识强化】第三章 存储系统 3.6 高速缓冲存储器
    【知识强化】第三章 存储系统 3.5 双口RAM和多模块存储器
  • 原文地址:https://www.cnblogs.com/wjyyy/p/lg2414.html
Copyright © 2011-2022 走看看