BZOJ3172——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


这道题需要我们求每个字符串在所有字符串构成的Trie里的出现次数。如果我们一个一个去匹配,那肯定会超时,所以我们要用更优的方法。这里就需要用每个节点的fail去构建一棵树,称为fail树。
如果我们每次将点x向它的fail相连,那么所得的树就会有一个非常神奇的特点,也就是从根到点fail[x]的子串必定为根到x的子串的后缀。所以也就是说,如果我们要统计字符串s在所有字串里出现的次数,就只需要在fail树上它的子树里找即可。具体的找法如下:我们采用从叶子节点到根的做法,先从最下面的叶子节点开始,将sz[fail[x]]加上sz[x],这样一直往上更新,就可以求出答案。因为更新是累加的,所以可以保证向上的递增,而由于每次都向上跳fail[x],所以可以保证跳到的是当前字串的后缀,可以继续累加答案,这样就保证了正确性。

Code:

#include<bits/stdc++.h>
using namespace std;
int read(){
    char c;int x;while(c=getchar(),c<'0'||c>'9');x=c-'0';
    while(c=getchar(),c>='0'&&c<='9') x=x*10+c-'0';return x;
}
int n,cnt,bel[1500000],q[1500000];
string s;
struct node{
    int fail,sz,next[26];
}F[1500000];
void build(string s,int num){
    int pl=0;
    for(int i=0;i<s.length();i++){
        if(F[pl].next[s[i]-'a']) pl=F[pl].next[s[i]-'a'];
        else{
            cnt++;F[pl].next[s[i]-'a']=cnt;pl=cnt;
        }
        F[pl].sz++;
    }
    bel[num]=pl;
}
void fail(){
    int h=0,t=0;
    for(int i=0;i<26;i++) if(F[0].next[i]) F[q[++t]=F[0].next[i]].fail=0;
    while(h<t){
        int now=q[++h];
        for(int i=0;i<26;i++)
         if(F[now].next[i]) F[q[++t]=F[now].next[i]].fail=F[F[now].fail].next[i];
         else F[now].next[i]=F[F[now].fail].next[i];
    }
}
void match(){
    for(int i=cnt;i>=0;i--) F[F[q[i]].fail].sz+=F[q[i]].sz;
    for(int i=1;i<=n;i++) printf("%d\n",F[bel[i]].sz);
}
int main()
{
    n=read();
    for(int i=1;i<=n;i++){
        cin>>s;build(s,i);
    }
    fail();
    match();
    return 0;
}

猜你喜欢

转载自blog.csdn.net/stevensonson/article/details/79919007