kmp与扩展kmp(板子整理)

kmp

next[i]:求模式串中前缀和后缀的最长公共前缀

即对于[1,i]来说,求最大的x,使s[1,x]等于s[i-x+1,i]

例题

以hdu1711为例,

统计a串中第一个出现b串的起始下标,

即若第一个出现b串的区间是[l,r],输出l

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int a[1000005],b[10005];
int n,m;
int nex[10005];
void kmppre(){
	int i=0,j=nex[0]=-1;
	while(i<m){
		while(j!=-1&&b[i]!=b[j])j=nex[j];
		nex[++i]=++j;
	}
}
int kmpcount(){
	int i,j,ans=0;
	kmppre();
	i=j=0;
	while(i<n){
		while(j!=-1&&a[i]!=b[j])j=nex[j];
		i++;j++;
		if(j==m)return i-j+1;
	}
	return -1;
}
int main(){
	int T;
	scanf("%d",&T);
	while(T--){
		scanf("%d%d",&n,&m);
		for(int i=0;i<n;++i)scanf("%d",&a[i]);
		for(int i=0;i<m;++i)scanf("%d",&b[i]);
		printf("%d\n",kmpcount());
	} 
	return 0;
} 

最小表示法与最大表示法

最小表示法:

对于串bca来说,其循环串abc,bca,cab中字典序最小的那个,串abc,即为最小表示

最大表示法同理

hdu3374,用kmp求一个串的最小表示法

//一个串在表示法里出现了几次 等于这个串的循环节在串中出现的次数 
//考虑一串珠子 往后转循环节个长度 还能得到一个与原串相同的串 这就是循环节的定义
//即循环节出现的次数 即为表示法中出现的次数 
#include <iostream>
#include <algorithm> 
#include <string>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <set>
#include <map>
#include <vector>
#include <stack>
#include <queue>
const int INF=0x3f3f3f3f;
const int MOD=1e9+7;
const double eps=1e-7;
typedef long long ll;
#define vi vector<int> 
#define si set<int>
#define pii pair<int,int> 
#define pi acos(-1.0)
#define pb push_back
#define mp make_pair
#define lowbit(x) (x&(-x))
#define sci(x) scanf("%d",&(x))
#define scll(x) scanf("%lld",&(x))
#define sclf(x) scanf("%lf",&(x))
#define pri(x) printf("%d",(x))
#define rep(i,j,k) for(int i=j;i<=k;++i)
#define per(i,j,k) for(int i=j;i>=k;--i)
#define mem(a,b) memset(a,b,sizeof(a)) 
using namespace std;
string s; 
int net[1000005];
int getminmax(int flag,string s)//min是0 max是1 注意flag的应用 
{
	int n=s.size();
	int i=0,j=1,k=0,t;
	while(i<n&&j<n&&k<n)
	{
		t=s[(i+k)%n]-s[(j+k)%n];
		if(!t)k++;
		else
		{
			if(!flag)t>0?i+=k+1:j+=k+1;
			else t>0?j+=k+1:i+=k+1;
			if(i==j)j++;
			k=0;
		}
	}
	return i<j?i:j;
}
void kmppre(string s)
{
	int i,j,n=s.size();
	i=0,j=net[0]=-1;
	while(i<n)
	{
		while(j!=-1&&s[i]!=s[j])j=net[j];
		net[++i]=++j;
	}
}
void init(int &t)
{
	mem(net,0);
	t=1;
}
int main()
{
	int len,minn,maxx,t;
	while(cin>>s)
	{
	 init(t);
	 kmppre(s);
	 len=s.size();
	 minn=getminmax(0,s);//求最小表示法 
	 maxx=getminmax(1,s);//求最大表示法 
	 if(len%(len-net[len])==0)t=len/(len-net[len]); 
	 printf("%d %d %d %d\n",1+minn,t,1+maxx,t);//minn的pos和maxx的pos 
    }
	return 0;
}

循环节

设m为串长,则m-next[m]是一个循环节,

m-next[next[m]]也是循环节,以此类推…

可以不断跳next数组获取到所有循环节

这些循环节并不是都能整除m,

能整除m的最短循环节最为常用

扩展kmp(Z函数)

next[i]:求模式串中前缀和后缀的最长公共前缀

即对于[1,i]来说,求最大的x,使s[1,x]等于s[i-x+1,i]

extend[i]: 求文本串中以i开始的一个后缀,和模式串中的前缀的最长公共前缀

即,对于文本串t[1,m]和模式串s[1,n]来说,

求最大的x,使得t[i,i+x-1]等于s[1,x]

替代方案

kmp可以取巧替代exkmp,并且实际效果比kmp快

t=s+'#'+t,即模式串在最前,中间用未出现的字符分隔,文本串在最后

则数组后半部分的next[i]即为所求

例题

以poj3080为例,

多个字符串,问你这几个字符串的最长公共子串是哪个,

如果有多个,输出字典序最大的那个,

如果最长的公共子串长度小于3,输出no significant commonalities

其实有点强行用exkmp的意思,暴力枚举每个串判是否在其他的串中存在,kmp也可以

#include <iostream>
#include <algorithm> 
#include <string>
#include <cstring>
#include <cstdio>
#include <cmath>
#define sci(x) scanf("%d",&(x))
#define scs(x) scanf("%s",(x))
#define scll(x) scanf("%lld",&(x))
#define sclf(x) scanf("%lf",&(x))
#define rep(i,j,k) for(int i=j;i<=k;++i)
#define mem(a,b) memset(a,b,sizeof(a)) 
using namespace std;
char a[15][65];
int net[65],ex[65];
char s1[65],s2[65],tmp[65];
void extkmppre(char s[])
{
	int i=0,j,pos,len=strlen(s);
	net[0]=len;
	while(i+1<len&&s[i]==s[i+1])i++;
	net[1]=i,pos=1;
	rep(i,2,len-1)
	{
		if(net[i-pos]+i<net[pos]+pos)
		{
			net[i]=net[i-pos];
		}
		else 
		{
			j=net[pos]+pos-i;
			if(j<0)j=0;
			while(i+j<len&&s[j]==s[i+j])j++;
			net[i]=j,pos=i;
		}
	}
}
bool extkmp(char s1[],char s2[])
{
	int i=0,j,pos,l1=strlen(s1),l2=strlen(s2);
	extkmppre(s2);
	while(i<l2&&i<l1&&s1[i]==s2[i])i++;
	ex[0]=i,pos=0;
	if(ex[0]==l2)return 1;
	rep(i,1,l1-1)
	{
		if(net[i-pos]+i<ex[pos]+pos)
		{
			ex[i]=net[i-pos];
		}
		else 
		{
			j=ex[pos]+pos-i;
			if(j<0)j=0;
			while(i+j<l1&&j<l2&&s1[i+j]==s2[j])j++;
			ex[i]=j,pos=i;	
		}
		if(ex[i]==l2)return 1;
	}
	return 0;
}
void cpy(char a[],char b[],int l,int r)
{
	rep(i,l,r)a[i-l]=b[i];
	a[r-l+1]='\0';
}
int main()
{
	int t;
	sci(t);
	while(t--)
	{
		mem(a,0);
		mem(tmp,0);
		int m,maxlen=0,l,r;
		sci(m);
		rep(i,0,m-1)
		scs(a[i]);
		rep(i,0,59)
		{
			rep(j,i+2,59)
			{
				if(j-i+1<maxlen)continue;//剪枝 
				bool flag=1;
				cpy(tmp,a[0],i,j);
				rep(k,1,m-1)
				{
					if(extkmp(a[k],tmp))continue;
					flag=0;
					break; 
				}
				if(flag)
				{
					if(j-i+1>maxlen)
					{
						maxlen=j-i+1;
						l=i,r=j;
					}
					else if(j-i+1==maxlen)
					{
						rep(k,0,maxlen-1)
						{
							if(a[0][k+i]>a[0][k+l])break;
							if(a[0][k+i]<a[0][k+l])
							{
								l=i,r=j;
								break;
							}
						}
					}
				}
			}
		}
		if(maxlen<3)puts("no significant commonalities");
		else 
		{
		    cpy(tmp,a[0],l,r);
		    printf("%s\n",tmp);
		} 
	}
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code92007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值