字符串算法:
1、 KMP算法
2、 MANACHER算法
3、 EX_KMP算法
KMP算法代码:
求Next数组源代码:
next[0]=next[1]=0; k=0;
for (i=2;i<=m;i++)
{ while (k>0&&T[i]!=T[k+1]) k=next[k];
if (T[i]==T[k+1]) k++;
next[i]=k;
}
KMP算法匹配源代码:
k=0; //k表示S串当前位匹配到T串第k位
for (i=1;i<=n;i++)
{
while (k>0&&S[i]!=T[k+1]) k=next[k]; //跳到可匹配处
if (S[i]==T[k+1]) k++;
if (k==m) { k=next[k]; ans++; }
} //ans表示出现次数
MANACHER算法代码:
ans[0]=ans[1]=0; p=1;
for (i=2;i<=n;i++) //枚举回文中点
{ ans[i]=max(0, min(ans[2*p-i],p+ans[p]-i) ); //不超出最右端就直接取
while (S[i-ans[i]]==S[i+ans[i]]) ans[i]++; //暴力扩展
if (i+ans[i]>p+ans[p]) p=i; } //更新最右断点
EX_KMP算法代码:(与MANACHER相似)
计算Next数组源代码:
next[0]=m;
for (i=0;T[i]==T[i+1];i++);
next[1]=i; p=1;
for (i=2;i<m;i++) {
u=p+next[p]; //u为最右端位置
if (i+next[i-p]<u) next[i]=next[i-p];//如果答案可以直接取
else { //如果答案需要探索
for (j=max(u-i,0);T[i+j]==T[j];j++);
next[i]=j; p=i;
}
}
与S串匹配源代码:
for (i=0;i<m&&S[i]==T[i];i++);
ex[0]=i; p=0;
for (i=1;i<n;i++) {
u=p+ex[p]; //u为最右端位置
if (i+next[i-p]<u) ex[i]=next[i-p]; //如果答案可以直接取得
else { //如果答案需要探索
for (j=max(u-i,0);j<=m&&S[i+j]==T[j];j++);
ex[i]=j; p=i;
}
}
字符串读入:
一般情况使用KMP、MANACHER算法前,字符串从STRING[1]开始记录,EX_KMP从STRING[0]开始记录。
KMP的应用:
1、 求字符串T在S中出现的次数
poj3461 Oulipo
做法:直接用KMP进行匹配。
2、求字符串中的循环节
poj2185 Milking Grid
做法:行方向和列方向分别找出最小循环节长R和C,面积S就是R*C。
HDU3746 Cyclic Nacklace
做法:
若有完整的循环节如图中例1,则无需添加。
否则就有残缺的循环节如图中例2,则求出补齐所需的花费。
具体代码片段如下:
int cir=Len-next[Len]; //循环节长
if (Len%cir==0&&cir!=Len) //是否完整
printf("0\n");
else printf("%d\n",cir-Len%cir);
MANACHER的应用
求回文长度
两种情况:
1、如字符串ababac 最长回文就是以S[3]为中点,长度为5的回文串。
2、如字符串babbac 最长回文就是以S[3]和S[4]两个字符为中点,长度为4的回文串。
情况2可以通过预处理转换为情况1。方法:每两个字符间插入一个从未在原串出现过的相同字符,如’#’、’|’、’*’…同时为了防止数组越界,头尾分别加入两个不同的奇怪字符。
用此方法处理的字符串babbac为 @#b#a#b#b#a#c#!
Len=Len*2+3
回文长度为ans[i]-1 (不包括添加的字符,可证明)
HDU3068 最长回文
做法:直接做MANACHER。
HDU3613 Best Reward
做法:做MANACHER同时判断,若回文串包括左端点,则记录pl[ans[i]-1]=true 表示前ans[i]-1个字符组成回文串。 若包括右端点,则记录pr[ans[i]-1]=true 表示后ans[i]-1个字符组成回文串。
具体代码片段如下:
if (i-ans[i]==1) pl[ans[i]-1]=1;
if (i+ans[i]==Len) pr[ans[i]-1]=1;
EX_KMP的应用
给出一个长度n的字符串S[0..n-1]
和一个长度m的字符串T[0..n-1]
问S的哪个后缀和T具有最长的公共前缀
HDU4333 Revolving Digits
做法:可以把数字再复制一遍,用EX_KMP匹配,求出每一个后缀与原数相同的数字个数Next[i],如果大于原长则相同,否则比较下一个不同的数字。
注意:要求比较的是不同的数字,如果有循环节,则需要除去循环节个数(KMP)。
附三个完整模板:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int kmp()
{
char t[100],s[100]; scanf("%s%s",t+1,s+1);
int next[100],ans=0,k;
int len1=strlen(t+1),len2=strlen(s+1);
next[0]=next[1]=k=0;
for (int i=2;i<=len1;i++)
{
while (k>0&&t[k+1]!=t[i]) k=next[k];
if (t[k+1]==t[i]) k++;
next[i]=k;
}
k=0;
for (int i=1;i<=len2;i++)
{
while (k>0&&t[k+1]!=s[i]) k=next[k];
if (t[k+1]==s[i]) k++;
if (k==len1) {
ans++; k=next[k];
}
}
printf("%d",ans);
return 0;
}
int manacher()
{
char t[100],s[100]; scanf("%s",t+1);
int len=2; s[1]='@'; s[2]='#';
for (int i=1;i<strlen(t);i++)
{
s[++len]=t[i]; s[++len]='#';
} s[++len]='!';
int ans[100],p=1; memset(ans,0,sizeof(ans));
for (int i=2;i<=len;i++)
{
ans[i]=max(0,min(ans[2*p-i],p+ans[p]-i));
while (s[i+ans[i]]==s[i-ans[i]]) ans[i]++;
if (ans[i]+i>ans[p]+p) p=i;
}
for (int i=2;i<=len-1;i++) printf("%c%d ",s[i],ans[i]-1);
return 0;
}
int Ex_kmp()
{
char t[100],s[100]; scanf("%s%s",t,s);
int next[100],ex[100];
int len1=strlen(t),len2=strlen(s);
next[0]=len1;
for (next[1]=0;t[next[1]]==t[next[1]+1];next[1]++);
int p=1;
for (int i=2;i<len1;i++)
{
int u=p+next[p];
if (i+next[i-p]<u) next[i]=next[i-p];
else {
int j;
for (j=max(u-i,0);t[i+j]==t[j];j++);
next[i]=j; p=i;
}
}
int i;
for (i=0;i<=len1&&s[i]==t[i];i++);
ex[0]=i; p=0;
for (i=1;i<len2;i++)
{
int u=p+ex[p];
if (i+next[i-p]<u) ex[i]=next[i-p];
else {
int j;
for (j=max(u-i,0);t[j]==s[i+j];j++);
ex[i]=j; p=i;
}
}
for (int i=0;i<len2;i++) printf("%d ",ex[i]);
return 0;
}
int main()
{
//kmp();
//manacher();
//Ex_kmp();
return 0;
}