扩展kmp(HDU4333)

1.思路

对于一个数413413,可以生成多少个数呢?不难注意到,因为它有两个循环节,所以生成的数每个都重复了两遍,循环节个数可以通过kmp快速求出,最后求出的答案都除以一个循环节个数即可,在此不再赘述,见我的另一篇博客:http://k-xzy.cf/?p=1165

剩下的内容就是求这个数重复两遍得到的新数(比如63就是得到6363)的每一个后缀与原数的公共前缀,这样只要分别比较公共前缀的下一位就可以了。这可以用扩展kmp在线性时间内做到。

2.扩展kmp

我们求字符串A的每一个后缀A[i...lena]与B的公共前缀长度,假设这个长度为ex[i]。

首先我们要求出一个next数组,表示B的每一个后缀B[i...lenb]与B的公共前缀长度,假设此时我们求出了所有的next和ex[0]到ex[i-1],正在求ex[i]。

设p为当前已经匹配的A数组的最远地方,k为使得其到达最远点的i,即p=max(ex[k]+k-1);(k<i)

假设i+next[i-k]<=p

举个例子,字符A是DDBACBC...,字符串B是BACBT...当前i=5,容易目测得p=6,k=2.那么i-k代表的是“BAC”这一段,这一段是已知的,现在我们在字符串B中的第二个B处看一看是否它的自身匹配还在p的范围内,如果在,那么这个自身匹配还是已知的,已经扩展了的,那么很容易得到ex[i]=next[i-k]。(有点晦涩难懂?多看几遍意会一下,再联系一下kmp的内容)

假设i+next[i-k]>p,那么我们要暴力继续扩展来求ex[i]了,A字符串中从p+1开始,B字符串中从p-i+1开始,当然如果p-i+1<0,就从0开始。

那么容易目测得复杂度是线性的。

3.代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<climits>
#include<iomanip>
#include<cmath>
#include<vector>
#include<algorithm>
using namespace std;
int T;
char s1[100005],s2[200005];
int ex[200005],ne[200005],nx[200005];
int ans1,ans2,ans3;
void getne(char *B){
	int a=0,i,j,p,l,le=strlen(B);
	ne[0]=le;
	while(a<le-1&&B[a]==B[a+1])a++;
	ne[1]=a;a=1;
	for(i=2;i<le;i++){
		l=ne[i-a];p=a+ne[a]-1;
		if(i+l>p){
			j=p-i+1;if(j<0)j=0;
			while(j+i<le&&B[j+i]==B[j])j++;
			ne[i]=j;a=i;
		}
		else ne[i]=l;
	}
}
void getex(char *A,char *B){
	int a=0,i,j,p,l,mil,la=strlen(A),lb=strlen(B);
	getne(B);
	mil=min(la,lb);
	while(a<mil&&A[a]==B[a])a++;
	ex[0]=a;a=0;//注意a要清0
	for(i=1;i<la;i++){
		l=ne[i-a];p=a+ex[a]-1;
		if(i+l>p){
			j=p-i+1;if(j<0)j=0;
			while(j<lb&&j+i<la&&A[i+j]==B[j])j++;
			ex[i]=j;a=i;
		}
		else ex[i]=l;
	}
}
void kmp(char *B){
	int i,j,le=strlen(B);
	nx[0]=0;
	for(i=1;i<le;i++){
		j=nx[i];
		while(1){
			if(B[i]==B[j]){nx[i+1]=j+1;break;}
			if(!j){nx[i+1]=0;break;}
			j=nx[j];
		}
	}
}
int main()
{
   	int i,j,la,lb,cnt=0,mod;
   	scanf("%d",&T);
   	while(T--){
   		cnt++;scanf("%s",s1);
   		strcpy(s2,s1);strcat(s2,s1);
   		getex(s2,s1);kmp(s1);
   		lb=strlen(s1);
   		mod=lb-nx[lb];
   		if(lb%mod==0)mod=lb/mod;
   		else mod=1;
   		ans1=ans2=ans3=0;
   		for(i=0;i<lb;i++){
   			if(ex[i]>=lb)ans2++;
   			else if(s1[ex[i]]<s2[i+ex[i]])ans3++;
   			else ans1++;
   		}
   		printf("Case %d: %d %d %d\n",cnt,ans1/mod,ans2/mod,ans3/mod);
   	}
   	return 0;
} 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值