zoukankan      html  css  js  c++  java
  • bzoj3172 AC自动机+fail树

    bzoj3172: [Tjoi2013]单词

    Time Limit: 10 Sec Memory Limit: 512 MB
    某人读论文,一篇论文是由许多单词组成。但他发现一个单词会在论文中出现很多次,现在想知道每个单词分别在论文中出现多少次。

    Input

    第一个一个整数N,表示有多少个单词,接下来N行每行一个单词。每个单词由小写字母组成,N<=200,单词长度不超过10^6

    Output

    输出N个整数,第i行的数字表示第i个单词在文章中出现了多少次。

    Sample Input
    3
    a
    aa
    aaa
    Sample Output
    6
    3
    1

    再是一道初识fail树的经典题,所谓fail树就是在构建完fail指针后,把所有的fail指针反向,变成一棵树,每个串终点所在fail树的子树中cnt的总和就是这个字符串的出现次数,于是每个点记录所在fail树的子树中cnt的数量,可以线性时间内出解
    这一大段话有点难理解(如果你是初学者的话),实际上在实现起来不用真正的建一棵fail树,只要在makefail完成后
    cnt[fail[heap[i]]]+=cnt[heap[i]];
    heap记录的是bfs的序列

    这里写代码片
    #include<cstdio>  ///fail树 
    #include<cstring>
    #include<iostream>
    #include<queue>
    
    using namespace std;
    
    int n;
    char w[1000010];
    int ch[1000010][30],tot=0,fail[1000010];
    int cnt[1000010];
    int word[256],tt=0,heap[1000010];
    
    void build(int bh)
    {
        int i,j,nw=0;
        int len=strlen(w);
        for (i=0;i<len;i++)
        {
            int x=w[i]-'a';
            if (!ch[nw][x]) ch[nw][x]=++tot;
            nw=ch[nw][x];
            cnt[nw]++;//新结点的经过次数++ 
        }
        word[bh]=nw;
        return;
    }
    //这道题中字典和需要匹配的字符是一样的,所以不用单独再把字典中的东西跑一遍AC自动机 
    //在建trie时就把经过的点cnt++,相当于匹配了一遍 
    
    void make()  //make fail
    {
        int i,j;
        queue<int> q;
        for (i=0;i<26;i++)
            if (ch[0][i]) 
               q.push(ch[0][i]);
        while (!q.empty())
        {
            int r=q.front();  //当前节点 
            q.pop();
            heap[++tt]=r;
            for (i=0;i<26;i++)
            {
    //          if (!ch[r][i])            这种写法和我下面用//标注的写法 
    //          {                         时间复杂度是一样的,都是可行的 
    //              ch[r][i]=ch[fail[r]][i];  只是思维和处理方式不大一样 
    //              continue;
    //          }
    //          fail[ch[r][i]]=ch[fail[r]][i];
                if (!ch[r][i]) continue;       //
                int f=fail[r];                 //
                while(f&&!ch[f][i]) f=fail[f]; //
                fail[ch[r][i]]=ch[f][i];       //
                q.push(ch[r][i]);
            }   
        }
        for (i=tt;i;i--)
           cnt[fail[heap[i]]]+=cnt[heap[i]];
        return;  //在进行这一步操作时一定要按照bfs的顺序!!!
    }
    
    int main()
    {
    //  memset(cnt,0,sizeof(cnt));    调用cstring进行memset操作超级费时 
    //  memset(fail,0,sizeof(fail));  我在下面会具体解释 
    //  memset(ch,0,sizeof(ch));
        scanf("%d",&n);
        for (int i=1;i<=n;i++)
        {
            scanf("%s",&w);
            build(i);
        }
        make();
        for (int i=1;i<=n;i++)
            printf("%d
    ",cnt[word[i]]);
        return 0;
    }

    交到bzoj上发现我的用时要比其他人多很多,自行探究后发现是memset的guo
    这里写图片描述
    上面的那一行是使用memeset后的用时
    下面这一行是去掉memset后的用时
    可见,memset要慎用

  • 相关阅读:
    Python 基础 -2.4.2 生成器,迭代器
    Python 基础 -2.4.1 装饰器
    Python 基础 -2.4 函数进阶,名称空间,闭包,高阶函数,递归,匿名函数,生产式,生成器,迭代器
    Python 基础 -2.3 函数
    python中字典,元组,列表和字符串之间的转换
    input和raw_input区别
    Python 基础 -2.2 文件操作
    Golang之AES/DES加密解密
    Golang与MySQL
    RTFM
  • 原文地址:https://www.cnblogs.com/wutongtong3117/p/7673647.html
Copyright © 2011-2022 走看看