KMP模板以及簡單的入門題總結
KMP模板
//kmp演算法的主要作用在於對next陣列的運用,所以這裡只給出next陣列的模板
//性質1:對於每一個長度len的子串,該子串的最小迴圈節為len-next[len]
//性質2:kmp的next不斷向前遞迴的過程可以保證對於每一個當前字首,都有一段字尾與之對應
#include<stdio.h>
#include<iostream>
#include<string.h>
using namespace std ;
const int maxn = 1e6+5;
int Next[maxn];
char mo[maxn];
int n2;
void GetNext()
{
int i=0,j=-1;
while(i<n2)
{
if(j==-1||mo[i]==mo[j]) {++i,++j,Next[i]=j;}
else j=Next[j];
}
return ;
}
int main()
{
scanf("%s",mo);
n2=strlen(mo);
Next[0]=-1;
GetNext();
return 0;
}
KMP第一題
HDU1711
題意就是給你兩個序列,讓你求B序列在A序列第一次出現(完全相同)的下標
本題就是KMP的模板題,將i指標指向A串,將j指標指向B串,如果匹配就繼續下一位的匹配,如果不匹配,將j跳轉到next[j],繼續向前匹配。
HDU1711程式碼
#include<stdio.h>
#include<iostream>
using namespace std ;
const int maxn = 1e6+5;
int Next[maxn];
int str[maxn];
int mo[maxn];
int n1,n2;
void GetNext()
{
int i=0,j=-1;
while(i<n2)
{
if(j==-1||mo[i]==mo[j]) {++i,++j,Next[i]=j;}
else j=Next[j];
}
return ;
}
int kmp()
{
int cnt=0;
int i=0,j=0;
while(i<n1)
{
if(j==-1||str[i]==mo[j]) i++,j++;
else j=Next[j];//next陣列尋找與當前字尾匹配最長的字首,省略不必要的查詢
if(j==n2)
return i-n2+1;//首地址
}
return -1;
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n1,&n2);
for(int i=0;i<n1;i++) scanf("%d",&str[i]);
for(int j=0;j<n2;j++) scanf("%d",&mo[j]);
Next[0]=-1;
GetNext();
printf("%d\n",kmp());
}
return 0;
}
KMP第二題
HDU1686
題意就是求B串在A串中的出現次數(可重疊
依舊是利用next陣列,當某次匹配完成之後,將ans++,然後把j跳轉到next[j],原理類似上一題的不匹配的情況,省略不必要的查詢。
HDU1686程式碼
#include<stdio.h>
#include<iostream>
#include<string.h>
using namespace std ;
const int maxn = 1e6+5;
int Next[maxn];
char str[maxn];
char mo[maxn];
int n1,n2;
void GetNext()
{
int i=0,j=-1;
while(i<n2)
{
if(j==-1||mo[i]==mo[j]) {++i,++j,Next[i]=j;}
else j=Next[j];
}
return ;
}
int kmp()
{
int cnt=0;
int i=0,j=0;
while(i<n1)
{
if(j==-1||str[i]==mo[j]) i++,j++;
else j=Next[j];
if(j==n2)
{
cnt++;
j=Next[j];//完成一次匹配,將j移動到最長的字首處,省略不必要的查詢
}
}
return cnt;
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
scanf("%s%s",mo,str);
n1=strlen(str);
n2=strlen(mo);
Next[0]=-1;
GetNext();
printf("%d\n",kmp());
}
return 0;
}
KMP第三題
HDU2087
題意就是求B串在A串中的出現次數(不可重疊)
做法和上題類似,只不過在每次完成匹配之後,要考慮不能重疊的問題,所以j不能跳轉到next[j],而應該從0重新開始,因為A中已經匹配過的字元不能再利用,所以就變成了A剩下的子串中找B出現的次數,所以j應該從0重新開始匹配。
HDU2087程式碼
#include<stdio.h>
#include<iostream>
#include<string.h>
using namespace std ;
const int maxn = 1e6+5;
int Next[maxn];
char str[maxn];
char mo[maxn];
int n1,n2;
void GetNext()
{
int i=0,j=-1;
while(i<n2)
{
if(j==-1||mo[i]==mo[j]) {++i,++j,Next[i]=j;}
else j=Next[j];
}
return ;
}
int kmp()
{
int cnt=0;
int i=0,j=0;
while(i<n1)
{
if(j==-1||str[i]==mo[j]) i++,j++;
else j=Next[j];
if(j==n2)
{
cnt++;
j=0;
}
}
return cnt;
}
int main()
{
while(scanf("%s",str)!=EOF)
{
n1=strlen(str);
if(n1==1&&str[0]=='#') break;
scanf("%s",mo);
n2=strlen(mo);
Next[0]=-1;
GetNext();
printf("%d\n",kmp());
}
return 0;
}
KMP第四題
HDU3746
本題題意為新增最少的字元使原字串變成周期至少為2的迴圈字串
用到模板裡所說的,長度為len的字串的最小迴圈節為len-next[len],求出最小迴圈節,算出最後應該補充多少就結束了。
求最小迴圈節的證明法請看此連結KMP求字串最小迴圈節
HDU3746程式碼
#include<stdio.h>
#include<iostream>
#include<string.h>
using namespace std ;
const int maxn = 1e6+5;
int Next[maxn];
char str[maxn];
char mo[maxn];
int n1,n2;
void GetNext()
{
int i=0,j=-1;
while(i<n2)
{
if(j==-1||mo[i]==mo[j]) {++i,++j,Next[i]=j;}
else j=Next[j];
}
return ;
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
scanf("%s",mo);
n2=strlen(mo);
Next[0]=-1;
GetNext();
int tmp=n2-Next[n2];
if(n2%tmp==0&&n2!=tmp)
{
printf("0\n");
}
else
{
printf("%d\n",tmp-n2%tmp);
}
}
return 0;
}
KMP第五題
HDU1358
本題題意為求給定字串中所有為迴圈串的字首,並輸出該字首的最後一個字元下標和週期
用到模板中給定的方法,只要對每個字串判定一下是否為迴圈串就可以了
HDU1358程式碼
#include<stdio.h>
#include<iostream>
#include<string.h>
using namespace std ;
const int maxn = 1e6+5;
int Next[maxn];
char str[maxn];
char mo[maxn];
int n1,n2;
void GetNext()
{
int i=0,j=-1;
while(i<n2)
{
if(j==-1||mo[i]==mo[j]) {++i,++j,Next[i]=j;}
else j=Next[j];
}
return ;
}
int main()
{
int cnt=1;
while(scanf("%d",&n2)!=EOF)
{
if(n2==0) break;
scanf("%s",mo);
Next[0]=-1;
GetNext();
printf("Test case #%d\n",cnt++);
for(int i=1;i<=n2;i++)
{
int tmp=i-Next[i];
if(i/tmp==1) continue;
if(i%tmp==0)
{
printf("%d %d\n",i,i/tmp);
}
}
printf("\n");
}
return 0;
}
KMP第六題
POJ2406
本題題意為求給定字串的最大週期,做法類似上題,直接上程式碼
POJ2406程式碼
#include<stdio.h>
#include<iostream>
#include<string.h>
using namespace std ;
const int maxn = 1e6+5;
int Next[maxn];
char str[maxn];
char mo[maxn];
int n1,n2;
void GetNext()
{
int i=0,j=-1;
while(i<n2)
{
if(j==-1||mo[i]==mo[j]) {++i,++j,Next[i]=j;}
else j=Next[j];
}
return ;
}
int main()
{
int cnt=1;
while(scanf("%s",mo)!=EOF)
{
n2=strlen(mo);
if(n2==1&&mo[0]=='.') break;
Next[0]=-1;
GetNext();
int tmp=n2-Next[n2];
if(n2%tmp==0) printf("%d\n",n2/tmp);
else printf("1\n");
}
return 0;
}
KMP第七題
POJ2752
本題題意為求出所有在後綴中出現過的字首的最後一個元素的下標
本題要考慮一下next陣列的本質,其實就是最長的出現在後綴中的字首,但是由於本題要求所有的而不是最長的,考慮到next陣列的遞迴過程,其實就是對每一個當前長度的字首,都有完全相同的字尾與之對應,所以就不斷遞迴next陣列即可求解。
POJ2752程式碼
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<stack>
using namespace std ;
const int maxn = 1e6+5;
int ans[maxn];
int Next[maxn];
char str[maxn];
char mo[maxn];
int n1,n2;
void GetNext()
{
int i=0,j=-1;
while(i<n2)
{
if(j==-1||mo[i]==mo[j]) {++i,++j,Next[i]=j;}
else j=Next[j];
}
return ;
}
int main()
{
int cnt;
while(scanf("%s",mo)!=EOF)
{
cnt=0;
n2=strlen(mo);
Next[0]=-1;
GetNext();
int j=n2;
while(j!=0)
{
ans[cnt++]=j;
j=Next[j];
}
for(int i=cnt-1;i>=0;i--)
{
printf("%d%c",ans[i],i==0?'\n':' ');
}
}
return 0;
}
KMP第八題
POJ3080
本題題意為求m個字串長度至少為3的最長公共子串
由於m只有10而且len小於60,我們可以選擇列舉某一個串的子串並用str.find()或者kmp驗證是否所有該子串在所有字串中出現過,也可以用經典的二分長度將height陣列分塊的字尾陣列做法
POJ3080(find解法
//由於只查詢是否出現過,演算法複雜度差距不大,所以這裡給出簡單一些的寫法
#include<stdio.h>
#include<algorithm>
#include<iostream>
#include<string.h>
using namespace std;
const int maxn = 65;
char str[maxn];
string ansstr;
string str2[maxn];
int main()
{
int n,t;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
for(int i=0;i<n;i++)
{
scanf("%s",str);
str2[i]=str;
}
int ans=0;
string tmp=str2[0];
int tmplen=tmp.size();
for(int i=0;i<tmplen;i++)
{
for(int j=1;i+j<=tmplen;j++)
{
int cnt=1;
string ss=tmp.substr(i,j);
for(int k=1;k<n;k++)
{
if(str2[k].find(ss)!=-1)
cnt++;
}
if(cnt==n)
{
if(ans<j)
{
ans=j;
ansstr=ss;
}
else if(ans==j)
{
ansstr=min(ansstr,ss);
}
}
}
}
if(ans<3) printf("no significant commonalities\n");
else printf("%s\n",ansstr.c_str());
}
}
POJ3080(字尾陣列寫法
//具體實現原理可以參考我的字尾陣列部落格
#include <iostream>
#include<algorithm>
#include <stdio.h>
#include <string.h>
using namespace std;
#define maxn 4005
const int INF = 0x3f3f3f3f;
int wa[maxn],wb[maxn],wsf[maxn],wv[maxn],sa[maxn];
int rank[maxn],height[maxn],s[maxn];
char str[15][65];
int t,lenn[maxn];
int belong[maxn];
int anspos;
int vis[65];
int cmp(int *r,int a,int b,int k)
{
return r[a]==r[b]&&r[a+k]==r[b+k];
}
void getsa(int *r,int *sa,int n,int m)
{
int i,j,p,*x=wa,*y=wb,*t;
for(i=0; i<m; i++) wsf[i]=0;
for(i=0; i<=n; i++) wsf[x[i]=r[i]]++;
for(i=1; i<m; i++) wsf[i]+=wsf[i-1];
for(i=n; i>=0; i--) sa[--wsf[x[i]]]=i;
p=1;
j=1;
for(; p<=n; j*=2,m=p)
{
for(p=0,i=n+1-j; i<=n; i++) y[p++]=i;
for(i=0; i<=n; i++) if(sa[i]>=j) y[p++]=sa[i]-j;
for(i=0; i<=n; i++) wv[i]=x[y[i]];
for(i=0; i<m; i++) wsf[i]=0;
for(i=0; i<=n; i++) wsf[wv[i]]++;
for(i=1; i<m; i++) wsf[i]+=wsf[i-1];
for(i=n; i>=0; i--) sa[--wsf[wv[i]]]=y[i];
t=x;
x=y;
y=t;
x[sa[0]]=0;
for(p=1,i=1; i<=n; i++)
x[sa[i]]=cmp(y,sa[i-1],sa[i],j)? p-1:p++;
}
}
void getheight(int *r,int n)
{
int i,j,k=0;
for(i=1; i<=n; i++) rank[sa[i]]=i;
for(i=0; i<n; i++)
{
if(k)
k--;
else
k=0;
j=sa[rank[i]-1];
while(r[i+k]==r[j+k])
k++;
height[rank[i]]=k;
}
}
int check(int x,int n)
{
for(int i=1;i<=n-1;i++)
{
if(height[i]<x) continue;
int cnt=0;
for(int j=0;j<=t;j++) vis[j]=0;
while(height[i]>=x&&i<=n-1)
{
if(!vis[belong[sa[i-1]]])
{
vis[belong[sa[i-1]]]=1;
cnt++;
}
i++;
}
if(!vis[belong[sa[i-1]]])
{
vis[belong[sa[i-1]]]=1;
cnt++;
}
if(cnt>=t)
{
anspos=sa[i-1];
return true;
}
}
return false;
}
int main()
{
int len,n;
int casee;
scanf("%d",&casee);
while(casee--)
{
scanf("%d",&t);
if(t==0) break;
n=0;
int pos=30;
for(int i=0;i<t;i++)
{
scanf("%s",str[i]);
lenn[i]=strlen(str[i]);
for(int j=0;j<lenn[i];j++)
{
s[n++]=str[i][j]-'A'+1;
belong[n-1]=i;
}
s[n++]=pos++;
}
s[n]=0;
getsa(s,sa,n,pos);
getheight(s,n);
int l=1,r=60,mid;
while(l<=r)
{
mid=(l+r)>>1;
if(check(mid,n)) l=mid+1;
else r=mid-1;
}
if(r<3) printf("no significant commonalities \n");
else
{
for(int i=anspos;i<anspos+r;i++)
printf("%c",s[i]-1+'A');
printf("\n");
}
}
return 0;
}
KMP第九題
HDU2594
本題題意是求既是A串中的字首又是B串中的字尾的最長長度。
如果我們將AB進行拼接,我們可以發現即為最長的即使字首又是字尾的子串,但是這裡有一個細節,就是如果這個長度大於,代表這個是拼接之後產生的,是不可取的,所以我們可以運用性質2來不斷減少這個長度,直到他滿足即可。
HDU2594程式碼
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<stack>
using namespace std ;
const int maxn = 1e6+5;
int Next[maxn];
char str[maxn];
char mo[maxn];
int n1,n2;
void GetNext()
{
int i=0,j=-1;
while(i<n2)
{
if(j==-1||mo[i]==mo[j]) {++i,++j,Next[i]=j;}
else j=Next[j];
}
return ;
}
int main()
{
int cnt;
while(scanf("%s%s",mo,str)!=EOF)
{
n1=strlen(str);
n2=strlen(mo);
int tmp=n2;
for(int i=n2;i<n2+n1;i++)
{
mo[i]=str[i-n2];
}
n2=n1+n2;
mo[n2]='\0';
Next[0]=-1;
GetNext();
int ans=Next[n2];
while(ans>min(tmp,n2-tmp))
{
ans=Next[ans];
}
if(ans==0)
{
printf("0\n");
continue;
}
for(int i=0;i<ans;i++)
printf("%c",mo[i]);
printf(" ");
printf("%d\n",ans);
}
return 0;
}
KMP第十題
HDU3336
本題題意為求字串的每個字首在整個字串中的出現次數。
我們想象性質二,如果next[j]對答案有一個貢獻,那麼這個貢獻在j中一定會再貢獻一次,而且j為結尾的字串對於總串產生的貢獻只有長度為j的子串,於是我們可以得到轉移方程,最後對所有字首的貢獻取和即為答案。
HDU3336程式碼
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<stack>
using namespace std ;
const int maxn = 1e6+5;
int Next[maxn];
char str[maxn];
char mo[maxn];
int dp[maxn];
int n1,n2;
void GetNext()
{
int i=0,j=-1;
while(i<n2)
{
if(j==-1||mo[i]==mo[j]) {++i,++j,Next[i]=j;}
else j=Next[j];
}
return ;
}
int kmp()
{
int cnt=0;
int i=0,j=0;
while(i<n1)
{
if(j==-1||str[i]==mo[j]) i++,j++;
else j=Next[j];
if(j==n2)
{
cnt++;
j=0;
}
}
return cnt;
}
int ans[maxn];
int main()
{
int cnt;
int t;
scanf("%d",&t);
while(t--)
{
scanf("%d%s",&n2,mo);
Next[0]=-1;
GetNext();
int ans=0;
dp[0]=0;
for(int i=1;i<=n2;i++)
{
dp[i]=dp[Next[i]]+1;
ans=(ans+dp[i])%10007;
}
printf("%d\n",ans);
}
return 0;
}
KMP第十一題
HDU4300
本題題意比較難讀懂,題意為給你一段密文的對映方式和一段密文+明文的字串,密文是完整的,而明文不一定是完整的,讓你新增最少的字元使他變為完整的密文+明文
如果讀懂題意,可以考慮給定字串中密文長度一定是的,所以我們可以將後半段的字元均按照對映換為密文,然後找到最長的既在字首中出現又在後綴中出現的子串,根據性質二和第九題的做法,不斷遞迴next直到找到符合條件的然後跳出即可,輸出時要注意明文密文的轉換
HDU4300程式碼
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<stack>
using namespace std ;
const int maxn = 1e5+5;
int Next[maxn];
char str[maxn];
char str2[maxn];
char mo[maxn];
int dp[maxn];
int mm[30];
int nn[maxn];
int n1,n2;
void GetNext()
{
int i=0,j=-1;
while(i<n2)
{
if(j==-1||mo[i]==mo[j]) {++i,++j,Next[i]=j;}
else j=Next[j];
}
return ;
}
int ans[maxn];
int main()
{
int cnt;
int t;
scanf("%d",&t);
while(t--)
{
scanf("%s%s",str,mo);
strcpy(str2,mo);
n2=strlen(mo);
n1=strlen(str);
for(int i=0;i<n1;i++)
{
mm[i]=str[i]-'a';
nn[str[i]-'a']=i;
}
for(int i=0;i<(n2+1)/2;i++)
{
mo[i]=nn[mo[i]-'a']+'a';
}
Next[0]=-1;
GetNext();
int ans=Next[n2];
while(ans>min((n2+1)/2,n2-(n2+1)/2))
{
ans=Next[ans];
}
for(int i=0;i<n2-ans;i++)
printf("%c",str2[i]);
for(int i=0;i<n2-ans;i++)
printf("%c",nn[str2[i]-'a']+'a');
printf("\n");
}
return 0;
}
KMP第十二題
HDU1238
多個字串的最長公共子串,只不過子串可以逆置出現,只要把第八題的做法反過來再找一次就好,直接上程式碼
#include<stdio.h>
#include<algorithm>
#include<iostream>
#include<string.h>
using namespace std;
const int maxn = 105;
char str[maxn];
string ansstr;
string str2[maxn];
int main()
{
int n,t;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
for(int i=0;i<n;i++)
{
scanf("%s",str);
str2[i]=str;
}
int ans=0;
string tmp=str2[0];
int tmplen=tmp.size();
for(int i=0;i<tmplen;i++)
{
for(int j=1;i+j<=tmplen;j++)
{
int cnt=1;
string ss=tmp.substr(i,j);
for(int k=1;k<n;k++)
{
if(str2[k].find(ss)!=-1)
cnt++;
}
if(cnt==n)
{
if(ans<j)
{
ans=j;
ansstr=ss;
}
else if(ans==j)
{
ansstr=min(ansstr,ss);