3172: [Tjoi2013]单词
Time Limit: 10 Sec Memory Limit: 512 MBSubmit: 5051 Solved: 2467
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或last数组上溯,给经过的的词尾结点加上1次出现次数
优化:
由上述算法可知,每个文本串(即每个单词)在AC自动机上的每个结点,都可以使 其延fail数组能走到的单词 的出现次数加1
因此,可以建出fail树,给每个单词在AC自动机上的每个结点标号都加1(打标记),意味着其父结点中词尾的出现次数增加1
但这里不上溯,在fail树上做一次dp(类似前缀和)
这里可以直接利用建立AC自动机时的队列,从后往前,每个元素对应fail树中的结点层数一定是从高到低的
处理重复单词:
若单词i与j相同
(i<j)
(
i
<
j
)
,就用链表把j接到i的后面,AC自动机的词尾结点上记录该词第一次出现的序号,即可
代码
#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<cstdlib>
using namespace std;
const int N=1000005;
int ch[N][30],val[N],lastnum[N],sum[N],q[N],tot=0,ans[N],f[N];
char st[N];
void insert(int t)
{
int u=0,n=strlen(st);
for (int i=0; i<n; i++){
int c=st[i]-'a';
if (!ch[u][c]) ch[u][c]=++tot;
u=ch[u][c];
sum[u]++;
}
if (val[u]==0) val[u]=lastnum[t]=t;
else lastnum[t]=val[u];
}
void pre()
{
int head=0,tail=0;
for (int i=0; i<26; i++) if (ch[0][i]>0) q[tail++]=ch[0][i];
while (head<tail){
for (int i=0; i<26; i++)
if(ch[q[head]][i]>0){
q[tail++]=ch[q[head]][i];
int u=f[q[head]];
while(u>0 && ch[u][i]==0) u=f[u];
f[ch[q[head]][i]]=ch[u][i];
}
head++;
}
for (int i=tail-1; i>=0; i--){
if (val[q[i]]>0) ans[val[q[i]]]=sum[q[i]];
sum[f[q[i]]]+=sum[q[i]];
}
}
int main()
{
int n;
scanf("%d",&n);
for (int i=1; i<=n; i++) scanf("%s",st),insert(i);
pre();
for (int i=1; i<=n; i++) printf("%d\n",ans[lastnum[i]]);
return 0;
}
miao~~~