字串演算法(KMP+MANACHER+EX_KMP)總結
字串演算法:
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;
}