KMP
面向问题
在一个文本串中查找单个模式串
思路
利用nex数组移动模式串
nex[i]=j
表示b[0..j] = b[i-j..i],取最大的y
若不存在这样的y 则取nex[i]为-1
每次调用nex函数相当于推进模式串
代码如下
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
char a[10000],b[10000];
int next[10000];
int N,M,i,j;
int main()
{
gets(a); N=strlen(a); // a为主串
gets(b); M=strlen(b); // b为匹配串
next[0]=-1;
j=-1;
/*
next[i]=j
表示b[0..j]=b[i-j..i]取最大的j
不存在这样的
*/
for (i=1; i<M; i++)
{
while (j!=-1 && b[i]!=b[j+1]) j=next[j];
if (b[i]==b[j+1]) j++;
next[i]=j;
} // 类似于递推
for (i=0; i<M; i++) cout<<next[i]<<' '; cout<<endl;
j=-1;
// 不断跳 主串上的指针和字母串上的指针都在跳来跳去 next函数 实际上就是让模式串不断移动
for (i=0; i<N; i++)
{
while (j!=-1 && a[i]!=b[j+1]) j=next[j];
if (a[i]==b[j+1]) j++; //
if (j==M-1) cout<<i-M+1<<endl,j=next[j]; //当前位置可以匹配
}
return 0;
}
exKMP
面向问题
给定主串S 和模式串T
求主串所有后缀和模式串的LCP
设辅助函数nex[i]表示T[i..m]与T的最长公共前缀长度。
利用nex函数进行快速计算(直接得到公式等)
其实还是利用nex函数推动模式串移动
设extend[1..k]已经算好,并且在以前的匹配过程中到达的最远位置是p。
最远位置严格的说就是i+extend[i]-1的最大值,不妨设这个取最大值的i是a
依据定义
S[a..p]=T[0..p-a+1]
可推得
S[k+1..p]=T[k-a+2..p-a+1]
这个时候我们就可以借助nex函数移动T了
设L=nex[k-a+2]
分两类计算 k+L<p 直接算,k+L>=p继续探测
具体细节参照ppt
完整代码
const int maxn=100010; //字符串长度最大值
int nex[maxn],ex[maxn]; //ex数组即为extend数组
//预处理计算nex数组
void GETnex(char *str)
{
int i=0,j,po,len=strlen(str);
nex[0]=len;//初始化nex[0]
while(str[i]==str[i+1]&&i+1<len)//计算nex[1]
i++;
nex[1]=i;
po=1;//初始化po的位置
for(i=2;i<len;i++)
{
if(nex[i-po]+i<nex[po]+po)//第一种情况,可以直接得到nex[i]的值
nex[i]=nex[i-po];
else//第二种情况,要继续匹配才能得到nex[i]的值
{
j=nex[po]+po-i;
if(j<0)j=0;//如果i>po+nex[po],则要从头开始匹配
while(i+j<len&&str[j]==str[j+i])//计算nex[i]
j++;
nex[i]=j;
po=i;//更新po的位置
}
}
}
//计算extend数组
void EXKMP(char *s1,char *s2)
{
int i=0,j,po,len=strlen(s1),l2=strlen(s2);
GETnex(s2);//计算子串的nex数组
while(s1[i]==s2[i]&&i<l2&&i<len)//计算ex[0]
i++;
ex[0]=i;
po=0;//初始化po的位置
for(i=1;i<len;i++)
{
if(nex[i-po]+i<ex[po]+po)//第一种情况,直接可以得到ex[i]的值
ex[i]=nex[i-po];
else//第二种情况,要继续匹配才能得到ex[i]的值
{
j=ex[po]+po-i;
if(j<0)j=0;//如果i>ex[po]+po则要从头开始匹配
while(i+j<len&&j<l2&&s1[j+i]==s2[j])//计算ex[i]
j++;
ex[i]=j;
po=i;//更新po的位置
}
}
}
/*
我们会发现算extend函数的代码和算nex函数是一模一样!
这是因为nex实际上以T为母串、T为子串的一个特殊“扩展的KMP”
*/
马拉车
Ma[i]表示i能向两边推(包括i)的最大距离,如果能求出Ma,则答案就是max(Ma)-1了(以i为中点的最长回文为2*Ma[i]-1,但这是加过字符后的答案,把加进去的字符干掉,最长回文就是Ma[i]-1)。
我们假设Ma[1~i-1]已经求好了,现在要求Ma[i]:
假设当前能达到的最右边为R,左边界为对应的中点为pos,j是i的对称点。
1、当i<R时
情况1:由于L~R是回文,所以Ma[i]=Ma[j](i的最长回文和j的最长回文相同)。
情况2:这种情况是另一种:j的最长回文跳出L了。那么i的最长回文就不一定是j的最长回文了,但蓝色的肯定还是满足的。
综上所述,Ma[i]=min(Ma[2*pos-i],R-i)。
2、当i>=R时
由于后面是未知的,于是只能暴力处理了,在暴力的时候记得更新pos。
来自kuangbin的模板
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int MAXN=110010;
char Ma[MAXN*2];
int Mp[MAXN*2];
char s[MAXN];
void Manacher(char s[],int len)
{
int l=0; Ma[l++]='$';
Ma[l++]='#';
for(int i=0;i<len;i++) {
Ma[l++]=s[i];
Ma[l++]='#';
}
Ma[l]=0;
int mx=0,id=0;
for(int i=0;i<l;i++) {
Mp[i]=mx>i ? min(Mp[2*id-i],mx-i):1;
while(Ma[i+Mp[i]]==Ma[i-Mp[i]]) Mp[i]++;
if(i+Mp[i]>mx) {
mx=i+Mp[i]; id=i;
}
}
}
/*
* abaaba
* i: 0 1 2 3 4 5 6 7 8 9 10 11 12 13
* Ma[i]: $ # a # b # a # a $ b # a #
* Mp[i]: 1 1 2 1 4 1 2 7 2 1 4 1 2 1
*/
int main()
{
while(scanf("%s",s)==1)
{
int len=strlen(s);
Manacher(s,len);
int ans=0;
for (int i=0;i<2*len+2;i++) ans=max(ans,Mp[i]-1);
printf("%d\n",ans);
}
return 0;
}