洛谷2375 BZOJ3670 NOI2014动物园 KMP

题目链接
题意:
给你一个长度为n的字符串,求对于每一个1到n之间的i,求字符串1到i的不重叠且相同的前缀与后缀的个数 n u m [ i ] num[i] num[i],输出 ∏ i = 1 n ( n u m [ i ] + 1 )   \prod_{i=1}^n(num[i]+1)\ %1000000007 i=1n(num[i]+1) 的结果。

题解:
根据题目的提示,很容易往KMP方向想。我们发现,只有next[i],next[next[i]]…这些位置会在字符串1-i中出现前缀与后缀相同的情况。
我们先不管不能重叠的限制,我们可以先求出可以重叠的情况,具体就是num[i]=num[next[i]]+1。然后我们再考虑不能重叠的限制,也就是对于前缀的末尾j,要满足 j &lt; i 2 j&lt;\frac{i}{2} j<2i
但是我们每一个i暴力往前跳next数组,直到跳到一个满足的j,复杂度是不对的,会被那种全是一种字符的字符串卡成 O ( n 2 ) O(n^2) O(n2),所以我们考虑这其中有很多次我们也是在做完之前的处理就知道可以不用重复跳的,这也是这题的巧妙之处,就是我们可以在满足这个j的限制的同时也像KMP那样跳过那些已经知道不需要再跳的位置。这样做的正确性似乎没大有人清楚的解释了,我的理解是跳到了一个当前i的第一个合法位置j之后,i+1的next要跳到一个不大于 i + 1 2 \frac{i+1}{2} 2i+1的第一个合法位置肯定是不会出现在j+1之后的,因为i的时候某个位置k如果不行,不可能在i+1时可能在k+1可以,因为只有出现了i位置的合法情况,后面一个也和i+1对应,才可能出现i+1的合法位置,都没出现过合法的i的对应j,更不可能出现i+1的了,所以接着上一个j继续做类似KMP的操作是正确的。

代码:

#include <bits/stdc++.h>
using namespace std;

int T,n,mod=1000000007,nxt[1000010],dep[1000010],num[1000010];
char s[1000010];
long long ans=1;
inline void getnext()
{
	int j=0;
	nxt[1]=0;
	dep[1]=1;
	for(int i=2;i<=n;++i)
	{
		while(j&&s[j+1]!=s[i])//ÕâÀïi¸Õ¼Ó¹ý£¬Ö®Ç°jºÍi-1Æ¥ÅäÁË 
		j=nxt[j];
		if(s[j+1]==s[i])//µ±Ç°jÌøµ½ÁËÓëiÏàͬµÄ 
		++j;
		nxt[i]=j;
		dep[i]=dep[j]+1;
	}
}
int main()
{
	scanf("%d",&T);
	while(T--)
	{
		ans=1;
		scanf("%s",s+1);		
		n=strlen(s+1);
		getnext();					
		int j=0;
		for(int i=2;i<=n;++i)
		{
			while(j&&s[j+1]!=s[i])
			j=nxt[j];
			if(s[j+1]==s[i])
			++j;
			while((j<<1)>i)
			j=nxt[j];
			ans=(long long)(ans*(dep[j]+1))%mod;
		}		
		printf("%d\n",ans);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值