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;
}