刚开始把题意理解错了囧
题意:给定n个字符串,求每个字符串在其他字符串中出现的次数之和
【题解】
首先肯定要建立AC自动机暴力算法:
以每个词为文本串做匹配,每匹配上一个位置,就从该节点延fail或last数组上溯,给经过的的词尾结点加上1次出现次数
优化:
由上述算法可知,每个文本串(即每个单词)在AC自动机上的每个结点,都可以使 其延fail数组能走到的单词 的出现次数加1
因此,可以建出fail树,给每个单词在AC自动机上的每个结点标号都加1(打标记),意味着其父结点中词尾的出现次数增加1
但这里不上溯,在fail树上做一次dp(类似前缀和)
这里可以直接利用建立AC自动机时的队列,从后往前,每个元素对应fail树中的结点层数一定是从高到低的
处理重复单词:
若单词i与j相同(i<j),就用链表把j接到i的后面,AC自动机的词尾结点上记录该词第一次出现的序号,即可
【代码】
字典树该建多大我真的不知道。。。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int ch[1000005][30],val[1000005],f[1000005],sum[1000005],q[1000005],lastnum[1000005],ans[1000005];
char s[1000005];
int sz=0;
void tj(int num)
{
int u=0,i,len=strlen(s);
for(i=0;i<len;i++)
{
if(ch[u][s[i]-96]==0) ch[u][s[i]-96]=++sz;
u=ch[u][s[i]-96];
sum[u]++;
}
if(val[u]==0) val[u]=lastnum[num]=num;
else lastnum[num]=val[u];
}
void build()
{
int head=0,tail=0,u,i;
for(i=1;i<=26;i++)
if(ch[0][i]>0) q[tail++]=ch[0][i];
while(head<tail)
{
for(i=1;i<=26;i++)
if(ch[q[head]][i]>0)
{
q[tail++]=ch[q[head]][i];
u=f[q[head]];
while(u>0&&ch[u][i]==0) u=f[u];
f[ ch[q[head]][i] ]=ch[u][i];
}
head++;
}
for(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,i;
scanf("%d",&n);
for(i=1;i<=n;i++)
{
scanf("%s",s);
tj(i);
}
build();
for(i=1;i<=n;i++)
printf("%d\n",ans[lastnum[i]]);
return 0;
}