BZOJ3172 [Tjoi2013]单词(AC自动机+打标记)

刚开始把题意理解错了囧

题意:给定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;
}


你可能感兴趣的:(AC自动机,递推,fail树,打标记)