luogu P3966 [TJOI2013]单词

背景:

暑假集训七月份最后一天,写写原来没有写过的 blog \text{blog} blog吧。

题目传送门:

https://www.luogu.org/problemnew/show/P3966

题意:

给出 n n n个单词构成的文章,求每一个单词分别在文章中出现了多次。

思路:

一种显然的思路就是 AC \text{AC} AC自动机,记录每一个点分别属于那一些单词的 end \text{end} end,每一次暴力在 fail \text{fail} fail树上跳。
然而这样时间复杂度没法保证, 90pts \text{90pts} 90pts

我们当然要考虑正解。
事实上,我们发现这个问题的本质就是统计 fail \text{fail} fail树的子树的大小(当然每一个加进来的点都要加上贡献)。
就可以 Θ ( n ) \Theta(n) Θ(n)出解了。

暴力 KMP \text{KMP} KMP也能跑过,因为字符集的大小有限。

代码

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<algorithm>
using namespace std;
	int n,len=0,st=0;
	char s[1000010];
	int end[210],ans[1000010],F[1000010];
queue<int> f;
struct node
{
	int fail;
	int son[30];
	node()
	{
		fail=0;
		memset(son,0,sizeof(son));
	}
} tr[1000010];
void build(char *s,int id)
{
	int l=strlen(s),x=0;
	for(int i=0;i<l;i++)
	{
		if(!tr[x].son[s[i]-'a']) tr[x].son[s[i]-'a']=++len;
		x=tr[x].son[s[i]-'a'];
		ans[x]++;
	}
	end[id]=x;
}
void get_fail()
{
	while(!f.empty()) f.pop();
	for(int i=0;i<26;i++)
		if(tr[0].son[i])
		{
			tr[tr[0].son[i]].fail=0;
			f.push(tr[0].son[i]);
		}
	while(!f.empty())
	{
		int x=f.front();
		F[++st]=x;
		f.pop();
		for(int i=0;i<26;i++)
		{
			int y=tr[x].son[i];
			if(y)
			{
				tr[y].fail=tr[tr[x].fail].son[i];
				f.push(y);
			}
			else tr[x].son[i]=tr[tr[x].fail].son[i];
		}
	}
}
void solve()
{
	for(int i=len;i>=0;i--)
		ans[tr[F[i]].fail]+=ans[F[i]];
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%s",s);
		build(s,i);
	}
	tr[0].fail=0;
	get_fail();
	solve();
	for(int i=1;i<=n;i++)
		printf("%d\n",ans[end[i]]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值