【bzoj3172】[Tjoi2013]单词 AC自动机+fail树

Description

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

Input

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

Output

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

Sample Input

3

a

aa

aaa

Sample Output

6

3

1

HINT

Source

fail树太神了…

fail[u]所代表的串,其实是u所代表的串的后缀,因为fail[u]可能很多,所以把边反向成一棵树,若点a指向点b,表明a所代表的串是b所代表的串的后缀,也就是出现了一次。

这样把每个串的每个字符所指向的点(也就是每个串的代表前缀的所有点)的权值都加1,表示这个串出现了一次,那么建出fail树,第i个串的答案就是第i个串结尾的点的标号的子树权值和…

关于代码实现,可以用逆拓扑序更新每个点的fail…Orzhzwer,手打队列还能这么玩…

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#include<stack>
using namespace std;

const int SZ = 1000010;

int ch[SZ][30],val[SZ],sz = 0;

int fail[SZ];

int d[SZ],id[SZ];

void insert(char s[],int num)
{
    int p = 0;
    int l = strlen(s);
    for(int i = 0;i < l;i ++)
    {
        int c = s[i] - 'a' + 1;
        if(!ch[p][c]) ch[p][c] = ++ sz;
        p = ch[p][c];
        d[p] ++;
    }
    val[p] ++;
    id[num] = p;
}

queue<int> q;
stack<int> S;

void build_ac()
{
    fail[0] = 0;
    for(int i = 1;i <= 26;i ++)
    {
        int u = ch[0][i];
        if(u)
        {
            fail[u] = 0;    
            q.push(u);
        }
    }
    while(q.size())
    {
        int f = q.front(); q.pop();
        S.push(f);
        for(int i = 1;i <= 26;i ++)
        {
            int u = ch[f][i];
            if(!u) continue;
            q.push(u);
            int v = fail[f];
            while(v && !ch[v][i]) v = fail[v];
            fail[u] = ch[v][i];
        }
    }
}


char s[SZ];

int main()
{
    int n;
    scanf("%d",&n);
    for(int i = 1;i <= n;i ++)
    {
        scanf("%s",s);
        insert(s,i);
    }
    build_ac();

    while(S.size())
    {
        int x = S.top(); S.pop();
        d[fail[x]] += d[x];
    }

    for(int i = 1;i <= n;i ++)
        printf("%d\n",d[id[i]]);

    return 0;
}

你可能感兴趣的:(【bzoj3172】[Tjoi2013]单词 AC自动机+fail树)