BZOJ3172:[TJOI2013]单词(AC自动机)
Description
某人读论文,一篇论文是由许多单词组成。但他发现一个单词会在论文中出现很多次,现在想知道每个单词分别在论文中出现多少次。
Input
第一个一个整数N,表示有多少个单词,接下来N行每行一个单词。每个单词由小写字母组成,N<=200,单词长度不超过10^6
Output
输出N个整数,第i行的数字表示第i个单词在文章中出现了多少次。
Sample Input
3
a
aa
aaa
a
aa
aaa
Sample Output
6
3
1
3
1
Solution
还是对AC自动机的理解不够到位啊=-=……
其实fail指针也是一颗树,而fail树有一些神奇的性质
根节点仍然是0,每一个fail指针指向自己的最长后缀
也就是说,我们把每个节点的权值设为1,然后再处理一下fail树的子树和
如何处理呢?在build_fail的时候记录一下经过节点的顺序,
然后倒序枚举,将当前枚举到的点的fail指针指向的点的权值累加一下
最开始记录一下每个单词的位置,方便输出。
其实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 }