BZOJ3172:[TJOI2013]单词(AC自动机)

Description

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

Input

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

Output

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

Sample Input

3
a
aa
aaa

Sample Output

6
3
1

Solution

还是对AC自动机的理解不够到位啊=-=……
其实fail指针也是一颗树,而fail树有一些神奇的性质
根节点仍然是0,每一个fail指针指向自己的最长后缀
也就是说,我们把每个节点的权值设为1,然后再处理一下fail树的子树和
如何处理呢?在build_fail的时候记录一下经过节点的顺序,
然后倒序枚举,将当前枚举到的点的fail指针指向的点的权值累加一下
最开始记录一下每个单词的位置,方便输出。

Code

 1 #include<iostream>
 2 #include<cstring>
 3 #include<cstdio>
 4 #include<queue>
 5 #define N (1000000+100)
 6 using namespace std;
 7 int Son[N][27],Fail[N],End[N];
 8 int n,sz,ans[N],pos[N],Q[N],cnt;
 9 bool vis[N],flag;
10 char s[N],ask[N];
11 queue<int>q;
12 void Insert(char s[],int &pos)
13 {
14     int now=0,len=strlen(s);
15     for (int i=0; i<len; ++i)
16     {
17         int x=s[i]-'a';
18         if (!Son[now][x]) Son[now][x]=++sz;
19         now=Son[now][x];
20         ++ans[now];
21     }
22     pos=now;
23 }
24 
25 void Build_Fail()
26 {
27     for (int i=0;i<26;++i)
28         if (Son[0][i])
29             q.push(Son[0][i]);
30     while (!q.empty())
31     {
32         int now=q.front(); q.pop();
33         Q[++cnt]=now;
34         for (int i=0;i<26;++i)
35         {
36             if (!Son[now][i])
37             {
38                 Son[now][i]=Son[Fail[now]][i];
39                 continue;
40             }
41             Fail[Son[now][i]]=Son[Fail[now]][i];
42             q.push(Son[now][i]);
43         }
44     }    
45 }
46 
47 void Query()
48 {
49     for (int i=cnt;i>=1;--i)
50         ans[Fail[Q[i]]]+=ans[Q[i]];
51     for (int i=1;i<=n;++i)
52         printf("%d\n",ans[pos[i]]);
53 }
54 int main()
55 {
56     scanf("%d",&n);
57     for (int i=1;i<=n;++i)
58         scanf("%s",s),Insert(s,pos[i]);
59     Build_Fail();
60     Query();
61 }
posted @ 2018-04-01 11:04  Refun  阅读(153)  评论(0编辑  收藏  举报