题解-[NOI2014]动物园

题目链接:

luogu

题目描述:

输入N组数据,每组数据包含一个字符串S,对于每个S,求出它的num数组。

num数组的定义是:字符串S的前i个字符构成的子串,既是它的后缀同时又是它的前缀,并且该后缀与该前缀不重叠,将这种字符串的数量记作num[i]。

为了避免大量的输出,你不需要输出num[i]分别是多少,你只需要输出所有(num[i]+1)的乘积,对1,000,000,007取模的结果即可。

题解:

这是一道很经典的KMP。还不会KMP的同学可以先看一下这篇博客,这里就不重复介绍了

那么这道题与普通的KMP有什么不同点呢?

我们发现最大的不同点有两个,一个是原来的next[i]表示的是字符串前i位的最长相同前后缀,而num[i]却要求出相同前后缀的总数。第二个是,这里要求后缀和前缀不重叠。

发现了不同点,那么就只要在原来的KMP代码上对于这两个点分别改动一下就行了

大家都知道(next[i]>0)表示字符串前i位有相同前后缀(如果next[i]=0就表示没有),而它的值就是最长的一个前缀的结尾。
OK,那么第一个点就迎刃而解了
第2长的公共前后缀一定比最长的短吧
那它一定包含在S[0]-S[next[i]]里面吧
那么这里面最长的是哪个?
next[next[i]]

以此类推,next[i],next[next[i]],next[next[next[next[i]]]…都是相同前缀的结尾,只要它们不是0,就代表num[i]多一个,那么把num[i]++就行了
是不是感觉和manacher有点像?

这样能解决第一个点,但不能解决第二个,因为这样求出来的前后缀可能会有重叠

现在解决第二个点就很简单啦,找到第一个小于一半的num,

while(k*2>j+1){
	k=next[k];
}

其它就和普通的KMP一样

完整代码:

#include <bits/stdc++.h>
using namespace std;
const int maxn=1e6+10;
const long long mod=1e9+7;
int next[maxn],num[maxn];
int main(){
	int n;
	cin>>n;
	
	for(int i=0;i<n;i++){
		memset(next,0,sizeof(next));
		memset(num,0,sizeof(num));
		string s;
		cin>>s;
		int len=s.size();
		int k=0;
		num[1]=1; 
		for(int j=1;j<len;j++){
			while(k>0&&s[j]!=s[k]){
				k=next[k];
			}
			if(s[j]==s[k]) k++;
			next[j+1]=k;
			num[j+1]=num[k]+1;
		}
		k=0;
		long long int ans=1;
		for(int j=1;j<len;j++){
			while(k>0&&s[j]!=s[k]){
				k=next[k];
			}
			if(s[j]==s[k]) k++;
			while(k*2>j+1){
				k=next[k];
			}
			ans=ans*(num[k]+1)%mod;
		}
		cout<<ans<<endl;
	}
	return 0;
}
创作时间:

2019-8-17

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值