POJ2406 & POJ1961 详细解题思路和AC程序 KMP算法变种

利用KMP算法解决POJ2406和POJ1961问题,计算next数组判断字符串最短重复子串,并寻找满足条件的前缀子串及其最大倍数k。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

poj2406

题目大意:

给出一个字符串s,求其最短的子串a,使得s=a^n(即n个a相连),输出n。s的长度小于10^6。


Sample Input

abcd
aaaa
ababab
.

Sample Output

1
4
3




本题可以用kmp算法来做。

先算出next数组。有些KMP算法的代码中next[i]代表的是s[i+1]匹配不上时下一个位置,有些代码代表的是s[i]匹配不上时,本人用后者,即next数组要求到'\0'那位。


len表示s的长度(不包括'\0'),那么如果确实有长度非len的解的话,该子串的最小长度一定为(len-next[len])。

以上不证明了(因为不会证),参考kmp算法的原理脑补一下:p

但长度为(len-next[len])的子串不一定真的是解。比如:abcab,len-next[len]=3,长度为3的串abc或者cab显然不是解

所以需要判断len%(len-next[len])==0是否成立。


够了吗? 那么是否有必要扫描一遍s,判断是否s中存在的确实是len/(len-next[len])个该子串呢?

够了。


如下图,表示len==12,next[len]==8时的情景,len-next[len]==4,可以发现s即为3个长度为4的子串构成的



参考程序:

#include <stdio.h>
#include <stdlib.h>
char s[1000010];
int next[1000010];

int kmp_next(char s[])
{
	int k=-1,j=0;
	next[j]=k;

	while (s[j])
	{
		if (k==-1 || s[k]==s[j])
		{
			k++;
			j++;
			if (s[k]==s[j])
				next[j]=next[k];
			else
				next[j]=k;
		}
		else k=next[k];
	}
	return j;
}

int main()
{
	int len,i,ans,flag,j;
	scanf("%s",s);
	while (s[0]!='.')
	{
		len=kmp_next(s);

		ans=(len-next[len]);
		if (len%ans!=0)
			ans=len;

		printf("%d\n",len/ans);
		scanf("%s",s);
	}
	return 0;
}


POJ1961


题目大意:

给出一个字符串的长度和字符串s,对于其所有的前缀,设其长度为i,求最大的k(k>=2),使a^k=substring(s,0,i),若存在这样的k,输出i,k

输出所有满足的i,k对


Sample Input

3
aaa
12
aabaabaabaab
0

Sample Output

Test case #1
2 2
3 3

Test case #2
2 2
6 2
9 3
12 4




跟上题差不多,不过这回需要对next数组进行遍历。

同时注意求next数组的时候

			if (s[k]==s[j])
				next[j]=next[k];
			else
				next[j]=k;
如果有该优化的话,该优化得去掉,改成:

  next[j]=k;

原因见kmp算法原理




参考程序:



#include <stdio.h>
#include <stdlib.h>

char s[1000010];
int next[1000010];

void kmp_next(char s[])
{
	int k=-1,j=0;
	next[j]=k;

	while (s[j])
	{
		if (k==-1 || s[k]==s[j])
		{
			k++;
			j++;
			next[j]=k;
		}
		else k=next[k];
	}
}

int main()
{
	int tt,len,i,l,j=0;
	scanf("%d",&len);
	gets(s);
	while (len!=0)
	{
		gets(s);
		kmp_next(s);

		printf("Test case #%d\n",++j);
		for (i=2;i<=len;i++)
		{
			l=i-next[i];
			if (i%l==0 && i/l>1)
				printf("%d %d\n",i,i/l);
		}

		printf("\n");
		scanf("%d",&len);
		gets(s);
	}
	return 0;
}



需要做更基础的kmp题的可以去做poj3461,poj2752




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值