【AC自动机】bzoj3172: [Tjoi2013]单词
fail图上后缀和需要注意一下
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
题目分析
考虑暴力:将每一个单词作为文本串匹配,一旦遇到一个单词节点,就向上跳fail统计整条链上的所有单词的贡献。
注意到对于每一个匹配到的单词节点,有一个向上的后缀和的形式在这里。
那么从另一个方式考虑,对于每一个节点,计算有多少子节点会把它统计进答案里。
这样就可以用很自然的fail后缀和来处理这个问题了。
注意
要注意的是,trie图后缀和的统计顺序不能够简单地根据tot...1的顺序。
因为trie图是有分叉的,节点的标号与深度并无关系。
1 #include<bits/stdc++.h> 2 const int maxn = 203; 3 const int maxNode = 1000035; 4 5 struct ACAutomaton 6 { 7 char s[maxn]; 8 int vis[maxNode]; 9 std::queue<int> q; 10 int stk[maxNode],cnt; 11 int fail[maxNode],f[maxNode][30],size[maxNode],tot,n; 12 void insert(char *s, int t) 13 { 14 int u = 1, lens = strlen(s); 15 for (int i=0; i<lens; i++) 16 { 17 int c = s[i]-'a'; 18 if (!f[u][c]) f[u][c] = ++tot; 19 u = f[u][c], size[u]++; 20 } 21 vis[t] = u; 22 // vis[u]++; 23 } 24 void count() //其实这里写的冗长了一点 25 { //如果用手写的队列就不用再开一个数组了 26 for (int i=tot; i>1; i--) 27 size[fail[stk[i]]] += size[stk[i]]; 28 } 29 void build() 30 { 31 for (int i=0; i<=25; i++) f[0][i] = 1; 32 q.push(1); 33 while (q.size()) 34 { 35 int tt = q.front(); 36 q.pop(); 37 stk[++cnt] = tt; 38 for (int i=0; i<=25; i++) 39 if (f[tt][i]) 40 fail[f[tt][i]] = f[fail[tt]][i], q.push(f[tt][i]); 41 else f[tt][i] = f[fail[tt]][i]; 42 } 43 } 44 }f; 45 int n; 46 char s[1000035]; 47 48 int main() 49 { 50 scanf("%d",&n); 51 f.tot = 1; 52 for (int i=1; i<=n; i++) 53 scanf("%s",s), f.insert(s, i); 54 f.build(), f.count(); 55 for (int i=1; i<=n; i++) printf("%d\n",f.size[f.vis[i]]); 56 return 0; 57 }
END