zoukankan      html  css  js  c++  java
  • BZOJ3172: [Tjoi2013]单词

    【传送门:BZOJ3172


    简要题意:

      给出n个单词,你可以理解为将这些单词变成一个个段落,然后求出每个单词在所有段落中出现的次数


    题解(一):

      刚开始不是很懂题目,结果发现将所有单词看成一篇文章,每个单词看成一个段落就懂了

      由于某种unbelievable的原因,我刚好做了AC自动机的专题训练,看到这道题就秒想AC自动机

      将每个单词放进AC自动机里,每个点的s表示有多少个单词经过,然后在构建失败指针的时候,通过队列来更新s值,怎么更新呢,假设有一个i点,它的失败指针指向j,设sj为从根到j点所构成的字符串,si为从根到i点所构成的字符串,那么我们可以知道sj为si的后缀,也说明了si中有sj的出现,那么就将j点的s值加上i点的s值,达到更新且不重复的目的来求出答案


     

    参考代码(一):

    #include<queue>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<cstdlib>
    #include<cmath>
    using namespace std;
    struct node
    {
        int s,c[27],fail;
        node()
        {
            s=fail=0;
            memset(c,-1,sizeof(c));
        }
    }t[1100000];
    int tot,ans,n;
    char a[1100000];
    int ed[210];
    void bt(int k,int root)
    {
        int x=root,len=strlen(a+1);
        for(int i=1;i<=len;i++)
        {
            int y=a[i]-'a'+1;
            if(t[x].c[y]==-1)
            {
                t[x].c[y]=++tot;
            }
            x=t[x].c[y];
            t[x].s++;
        }
        ed[k]=x;
    }
    int list[1100000];
    void bfs()
    {
        int x;
        int head=1,tail=1;
        list[1]=0;
        while(head<=tail)
        {
            x=list[head];
            for(int i=1;i<=26;i++)
            {
                int son=t[x].c[i];
                if(son==-1)continue;
                if(x==0) t[son].fail=0;
                else
                {
                    int j=t[x].fail;
                    while(j!=0&&t[j].c[i]==-1) j=t[j].fail;
                    t[son].fail=max(t[j].c[i],0);
                    int x=t[son].fail,y=son;
                }
                list[++tail]=son;
            }
            head++;
        }
        for(int i=tail;i>=1;i--) t[t[list[i]].fail].s+=t[list[i]].s;
    }
    int main()
    {
        ans=tot=0;
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
        {
            scanf("%s",a+1);
            bt(i,0);
        }
        bfs();
        for(int i=1;i<=n;i++)
        {
            printf("%d
    ",t[ed[i]].s);
        }
        return 0;
    }

     


     

    题解(二):

        A了这道题之后才发现早在之前就A了(心酸)

      那我们就来搞一下第二种想法

      很容易就想到AC自动机,但是单单是AC自动机还不行

      这时就要用AC自动机的延伸——fail树来做(时常膜一膜算法,有益身体健康)

      fail树这个玩意就是将AC自动机中fail指针当成是一条边,然后建成一棵树

      由于trie树上的每个点相当于一个字符串,所以这棵fail树父亲节点是儿子节点所构成字符串的最长后缀

      这样子对于这道题而言,fail树简直就是神一般的存在QAQ

      首先将每个字符串放进trie树里面,并且每经过一个trie树里的点,就用s数组记录这个字符串出现的个数,每经过一次就加一,然后构造AC自动机,然后在构造AC自动机的过程中,对每个点的fail指针进行建边,然后用dfs从根往下遍历,把s数组从下往上累加

      然后在输入每个字符串的时候,记录每个字符串的最后一个字符在trie树上的编号,然后一个一个输出s[end[i]](end[i]表示第i个字符串最后一个字母在trie树上的编号)


    参考代码(二):

    #include<queue>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<cstdlib>
    #include<cmath>
    using namespace std;
    struct node
    {
        int c[27],fail;
        node()
        {
            fail=0;
            memset(c,-1,sizeof(c));
        }
    }t[1100000];
    int tot,n;
    char st[1100000];
    int s[1100000];
    int end[1100000];
    void bt(int root,int z)
    {
        int x=root,len=strlen(st+1);
        for(int i=1;i<=len;i++)
        {
            int y=st[i]-'a'+1;
            if(t[x].c[y]==-1)
            {
                t[x].c[y]=++tot;
            }
            x=t[x].c[y];s[x]++;
        }
        end[z]=x;
    }
    struct edge
    {
        int x,y,next;
    }a[1100000];int len,last[1100000];
    void ins(int x,int y)
    {
        len++;
        a[len].x=x;a[len].y=y;
        a[len].next=last[x];last[x]=len;
    }
    queue<int> q;
    void bfs()
    {
        int x;
        q.push(0);
        while(q.empty()==0)
        {
            x=q.front();
            for(int i=1;i<=26;i++)
            {
                int son=t[x].c[i];
                if(son==-1)continue;
                if(x==0) t[son].fail=0;
                else
                {
                    int j=t[x].fail;
                    while(j!=0&&t[j].c[i]==-1) j=t[j].fail;
                    t[son].fail=max(t[j].c[i],0);
                }
                ins(t[son].fail,son);
                q.push(son);
            }
            q.pop();
        }
    }
    void dfs(int x)
    {
        for(int k=last[x];k;k=a[k].next)
        {
            int y=a[k].y;
            dfs(y);
            s[x]+=s[y];
        }
    }
    int main()
    {
        tot=0;
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
        {
            scanf("%s",st+1);
            bt(0,i);
        }
        len=0;memset(last,0,sizeof(last));
        bfs();
        dfs(0);
        for(int i=1;i<=n;i++) printf("%d
    ",s[end[i]]);
        return 0;
    }

     

  • 相关阅读:
    Visual Studio 2010使用Visual Assist X的方法
    SQL Server 2000 评估版 升级到 SQL Server 2000 零售版
    双网卡多网络单主机同时访问
    开发即过程!立此纪念一个IT新名词的诞生
    delphi dxBarManager1 目录遍历 转为RzCheckTree2树
    5320 软件集合
    delphi tree 从一个表复制到另一个表
    DELPHI 排课系统课表
    长沙金思维 出现在GOOGLE的 金思维 相关搜索里啦!!
    如何在DBGrid的每一行前加一个单选框?
  • 原文地址:https://www.cnblogs.com/Never-mind/p/7628734.html
Copyright © 2011-2022 走看看