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要慎用

原文地址:https://www.cnblogs.com/wutongtong3117/p/7673647.html