KMP专题练习记录
时间复杂度证明:
设\(for\)循环中串长为\(m\),\(j\)最多加\(m\)次,while循环中保证\(j>=0\),那么\(j\)在while中最多跳\(m\)次。
所以单次的KMP的主过程,复杂度为\(O(2m)\),总复杂度为\(O(2n+2m) = O(n+m)\)
\(\rule[0pt]{38.3cm}{0.05cm}\)
HDU 1711 Number Sequence
Solution:
裸的kmp
Code:
#include <iostream>
using namespace std;
int n,m;
const int N=2e6+10;
int s[N],p[N];
int Next[N];
void get_next(int p[],int lenp){
int j=0;
for(int i=2;i<=lenp;++i){
while(j&&p[j+1]!=p[i])j=Next[j];
if(p[j+1]==p[i])j++;
Next[i]=j;
}
}
int kmp(int s[],int lens,int p[],int lenp){
int j=0;
int ans=-1;
for(int i=1;i<=lens;++i){
while(j&&p[j+1]!=s[i])j=Next[j];
if(s[i]==p[j+1]){
j++;
}
if(j==lenp){
ans=i-lenp+1;
break;
}
}
return ans;
}
int main()
{
//freopen("in.txt","r",stdin);
int t;
scanf("%d",&t);
while(t--){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i){
scanf("%d",&s[i]);
}
for(int i=1;i<=m;++i){
scanf("%d",&p[i]);
Next[i]=0;
}
get_next(p,m);
printf("%d",kmp(s,n,p,m));
puts("");
}
return 0;
}
\(\rule[0pt]{38.3cm}{0.05cm}\)
HDU 1686 Oulipo
Solution:
kmp。每次p串匹配完后,不立刻从起点重新匹配,跳next数组即可。
Code:
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int Next[N];
int t;
char s[N],p[N];
void get_next(char o[],int leno){
int j=0;
for(int i=2;i<=leno;++i){
while(j&&o[j+1]!=o[i])j=Next[j];
if(o[j+1]==o[i])j++;
Next[i]=j;
}
}
int kmp(char s[],int lens,char p[],int lenp){
int j=0;
int cnt=0;
for(int i=1;i<=lens;++i){
while(j&&s[i]!=p[j+1])j=Next[j];
if(s[i]==p[j+1])j++;
if(j==lenp){
j=Next[j];
cnt++;
}
}
return cnt;
}
int main(){
//freopen("in.txt","r",stdin);
scanf("%d",&t);
while(t--){
scanf("%s%s",p+1,s+1);
int lens,lenp;
lens=strlen(s+1);
lenp=strlen(p+1);
get_next(p,lenp);
printf("%d\n",kmp(s,lens,p,lenp));
}
return 0;
}
\(\rule[0pt]{38.3cm}{0.05cm}\)
HDU 2087 剪花布条
Solution:
KMP。每次匹配完完整的模式串后,模式串从起点重新开始匹配。
Code:
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+10;
char s[N],p[N];
int Next[N];
#define debug(x) cout<<#x<<" :"<<x<<endl
void get_next(char p[],int lenp){
int j=0;
for(int i=2;i<=lenp;++i){
while(j&&p[i]!=p[j+1])j=Next[j];
if(p[i]==p[j+1])j++;
Next[i]=j;
}
}
int kmp1(char s[],int lens,char p[],int lenp){
int j=0;
int cnt=0;
for(int i=1;i<=lens;++i){
while(j&&p[j+1]!=s[i])j=Next[j];
if(p[j+1]==s[i])j++;
if(j==lenp){
j=0;
cnt++;
}
}
return cnt;
}
int main(){
//freopen("in.txt","r",stdin);
while(scanf("%s",s+1)){
if(s[1]=='#')break;
scanf("%s",p+1);
get_next(p,strlen(p+1));
int lens=strlen(s+1);
int lenp=strlen(p+1);
int ans=kmp1(s,lens,p,lenp);
printf("%d\n",ans);
}
return 0;
}
\(\rule[0pt]{38.3cm}{0.05cm}\)
HDU 3746 Cyclic Nacklace
Solution:
KMP。最小循环节为\(len-Next[len]\),对答案分类讨论。最小循环节证明博客。
Code:
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+10;
int t;
char s[N];
int Next[N];
void get_next(char p[],int lenp){
int j=0;
for(int i=2;i<=lenp;++i){
while(j&&p[j+1]!=p[i])j=Next[j];
if(p[j+1]==p[i]){
j++;
}
Next[i]=j;
}
}
int main(){
//freopen("in.txt","r",stdin);
scanf("%d",&t);
while(t--){
scanf("%s",s+1);
int lens=strlen(s+1);
get_next(s,lens);
int len=lens;
if(Next[len]==0){
printf("%d\n",len);
}
else {
if(len%(len-Next[len])==0)puts("0");
else printf("%d\n",((len/(len-Next[len]))+1)*(len-Next[len])-len);
}
}
return 0;
}
\(\rule[0pt]{38.3cm}{0.05cm}\)
HDU 2594 Simpsons’ Hidden Talents
Solution:
KMP。将两个串拼接起来,作一次求Next数组,答案为\(min(lens,lenp,Next[lens+lenp])\),最后注意输出格式即可。
Code:
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+10;
int t;
char s[N];
char p[N];
char r[N];
int Next[N];
void get_next(char p[],int lenp){
int j=0;
for(int i=2;i<=lenp;++i){
while(j&&p[i]!=p[j+1])j=Next[j];
if(p[i]==p[j+1])j++;
Next[i]=j;
}
}
int main(){
//freopen("in.txt","r",stdin);
while(scanf("%s%s",s+1,p+1)!=EOF){
int lens=strlen(s+1);
int lenp=strlen(p+1);
int lenr=lens+lenp;
for(int i=1;i<=lenr;++i){
if(i<=lens)r[i]=s[i];
else r[i]=p[i-lens];
}
get_next(r,lenr);
int ans=Next[lenr];
ans=min(ans,lens);
ans=min(ans,lenp);
for(int i=1;i<=ans;++i){
if(i!=ans)printf("%c",s[i]);
else printf("%c ",s[i]);
}
printf("%d\n",ans);
}
}
\(\rule[0pt]{38.3cm}{0.05cm}\)
HDU 1358 Period
Solution:
KMP。最小循环节直接做即可,注意整除关系。
Code:
#include<bits/stdc++.h>
using namespace std;
const int N=2e6+10;
char s[N];
int Next[N];
int len;
void get_Next(char p[],int lenp){
int j=0;
for(int i=2;i<=lenp;++i){
while(j&&p[i]!=p[j+1])j=Next[j];
if(p[i]==p[j+1])j++;
Next[i]=j;
}
}
int main(){
//freopen("in.txt","r",stdin);
int t;
int vc=0;
while(scanf("%d",&len)){
if(len==0)break;
scanf("%s",s+1);
get_Next(s,len);
printf("Test case #%d\n",++vc);
for(int i=2;i<=len;++i){
int T=i-Next[i];
if(i%T==0&&i/T!=1){
printf("%d %d\n",i,i/T);
}
}
puts("");
}
return 0;
}
\(\rule[0pt]{38.3cm}{0.05cm}\)
POJ 3080 Blue Jeans
Solution:
KMP。固定一个DNA,暴力其子串对其他DNA作KMP即可,注意输出要求。
Code:
#include<iostream>
#include<string.h>
#include<algorithm>
#include<stdio.h>
using namespace std;
const int N=11;
const int M=61;
char s[N][M];
char p[M];
int t,n;
int Next[M];
char ansc[M];
string kep[M*M];
void get_next(char q[],int lenq){
int j=0;
for(int i=2;i<=lenq;++i){
while(j&&q[j+1]!=q[i])j=Next[j];
if(q[j+1]==q[i])j++;
Next[i]=j;
}
}
bool kmp(char s[],int lens,char q[],int lenq){
int j=0;
for(int i=1;i<=lens;++i){
while(j&&q[j+1]!=s[i])j=Next[j];
if(q[j+1]==s[i])j++;
if(j==lenq){
return true;
}
}
return false;
}
bool cmp(string a,string b){
if(a.length()==b.length()){
return a<b;
}
return a.length()>b.length();
}
int main(){
//freopen("in.txt","r",stdin);
scanf("%d",&t);
while(t--){
scanf("%d",&n);
for(int i=1;i<=n;++i){
scanf("%s",s[i]+1);
}
int mx=0;
int cntkep=0;
for(int i=1;i<=60;++i){
for(int j=i;j<=60;++j){
int con=0;
memset(p,0,sizeof p);
for(int h=i;h<=j;++h)p[++con]=s[1][h];
get_next(p,con);
int cnt=0;
for(int z=2;z<=n;++z){
if(kmp(s[z],60,p,con))cnt++;
}
if(cnt==n-1){
mx=max(con,mx);
string op;
for(int h=1;h<=con;++h)op+=p[h];
kep[++cntkep]=op;
}
}
}
if(mx<3){
puts("no significant commonalities");
}
else{
sort(kep+1,kep+1+cntkep,cmp);
cout<<kep[1];
puts("");
}
}
return 0;
}
\(\rule[0pt]{38.3cm}{0.05cm}\)
POJ 2752 Seek the Name, Seek the Fame
Solution:
KMP。Next数组性质的应用,后缀与前缀关系具有传递性。
Code:
#include<stdio.h>
#include<algorithm>
#include<string.h>
#include<vector>
#include<iostream>
using namespace std;
const int N=5e5+10;
#define all(x) x.begin(),x.end()
int Next[N];
char s[N];
void get_next(char p[],int lenp){
int j=0;
for(int i=2;i<=lenp;++i){
while(j&&p[j+1]!=p[i])j=Next[j];
if(p[j+1]==p[i])j++;
Next[i]=j;
}
}
int main(){
while(scanf("%s",s+1)!=EOF){
int len=strlen(s+1);
get_next(s,len);
vector<int>ans;
int pos=len;
while(Next[pos]){
ans.push_back(Next[pos]);
pos=Next[pos];
}
ans.push_back(len);
sort(all(ans));
int sz=ans.size();
for(int i=0;i<sz;++i){
if(i==sz-1)printf("%d",ans[i]);
else printf("%d ",ans[i]);
}
puts("");
}
}
\(\rule[0pt]{38.3cm}{0.05cm}\)
POJ 2406 Power Strings
Solution:
KMP。Next数组性质以及循环节的应用,记\(pos=len\),在\(pos\)上不断跳\(Next\),由于前缀和后缀关系的传递性,每次跳Next之前的循环节为\(len-Next[pos]\),即可求出所有可能的循环节长度。细节见代码。
Code:
#include<stdio.h>
#include<algorithm>
#include<string.h>
#include<iostream>
using namespace std;
const int N=2e6+10;
char s[N];
int Next[N];
void get_next(char p[],int lenp){
int j=0;
for(int i=2;i<=lenp;++i){
while(j&&p[j+1]!=p[i])j=Next[j];
if(p[j+1]==p[i])j++;
Next[i]=j;
}
}
int main(){
while(scanf("%s",s+1)){
if(s[1]=='.')break;
int len=strlen(s+1);
get_next(s,strlen(s+1));
int mx=1;
int pos=len;
while(1){
int T=len-Next[pos];//所有可能的循环节长度。
if(len%T){
pos=Next[pos];
}
else {
mx=max(mx,len/T);
break;
}
}
printf("%d\n",mx);
}
return 0;
}
\(\rule[0pt]{38.3cm}{0.05cm}\)
HDU 3336 Count the string
Solution:
KMP-Next数组的应用。对于每个位置\(i\),暴力跳Next,就可以统计所有前缀出现次数,因为短的前缀会被长的前缀包含着。
Code:
#include<bits/stdc++.h>
using namespace std;
const int N=2e6+10;
char s[N];
int Next[N];
int
t;
const int mod=1e4+7;
void get_next(char p[],int lenp){
int j=0;
for(int i=2;i<=lenp;++i){
while(j&&p[j+1]!=p[i])j=Next[j];
if(p[j+1]==p[i])j++;
Next[i]=j;
}
}
int main(){
scanf("%d",&t);
while(t--){
int len;
scanf("%d",&len);
scanf("%s",s+1);
get_next(s,len);
int sum=0;
for(int i=1;i<=len;++i){
int pos=i;
int cnt=0;
while(pos){
pos=Next[pos];
cnt++;
}
sum=(sum+cnt)%mod;
}
printf("%d\n",sum);
}
return 0;
}
\(\rule[0pt]{38.3cm}{0.05cm}\)
SPOJ EPALIN Extend to Palindrome
Solution:
1.KMP-Next数组的应用。满足题意,可以将原串\(S1\)倒置为\(S2\),往原串后面放,结果为\(S1+S2\),但不是最优结果。考虑如何删减长度。
2.注意到回文的性质,从前往后读等于从后往前读,那么可以删减的部分满足以中间为对称轴,左串\(S1\)的后缀与右串\(S2\)的前缀相同。
3.考虑使删减长度最大。将原串倒置一份,往原串头部插入,构造新的字符串\(S3=S2+S1\),做一遍求Next数组,最大删减长度为\(min(Next[length(S3)],length(S2))\)。最后调整格式输出即可。
Code:
#include<bits/stdc++.h>
using namespace std;
const int N=2e6+10;
char newc[N];
char s[N];
int Next[N];
void get_next(char p[],int lenp){
int j=0;
for(int i=2;i<=lenp;++i){
while(j&&p[j+1]!=p[i])j=Next[j];
if(p[j+1]==p[i])j++;
Next[i]=j;
}
}
int main(){
while(scanf("%s",s+1)!=EOF){
int lens=strlen(s+1);
int con=0;
for(int i=lens;i>=1;i--){
newc[++con]=s[i];
}
for(int i=1;i<=lens;++i){
newc[++con]=s[i];
}
get_next(newc,lens*2);
int mxlen = Next[lens*2];
mxlen = min(mxlen,lens);
for(int i=1;i<=lens;++i){
printf("%c",s[i]);
}
for(int i=lens-mxlen;i>=1;i--){
printf("%c",s[i]);
}
puts(" ");
}
return 0;
}
\(\rule[0pt]{38.3cm}{0.05cm}\)
CodeForces 126B Password
Solution:
1.KMP-Next数组的应用。记\(len=length(S)\),那么\(Next[len]=0\)时显然无解。
2.观察得到,假设答案为\(ans\),那么一定会有中间的子串的\(Next\)值大于等于\(ans\),至于为什么是大于等于。
例子:abcdeababc,Next[len]=3。
假设\(ans=3\),发现其中间的子串有一个\(Next\)值等于2的串\("ab"\),发现\("ab"\)能在前缀中出现,但无法出现在后缀中。
所以大于等于的意义在于,在假定答案\(ans\)的前提下,满足于前缀相同且将后缀包含住。
3.讨论\(ans\)的可能取值。很显然,\(ans\)的可能取值一定会在\(len\)值不断跳\(Next\)中得到。
4.对于所有的可能\(ans\)的取值,若不存在一个\(ans\)满足第二点,则无解。
Code:
#include<bits/stdc++.h>
using namespace std;
const int N=2e6+10;
char s[N];
int Next[N];
int vis[N];
void get_next(char p[],int lenp){
int j=0;
for(int i=2;i<=lenp;++i){
while(j&&p[j+1]!=p[i]){
j=Next[j];
}
if(p[j+1]==p[i]){
j++;
}
Next[i]=j;
}
}
int main(){
scanf("%s",s+1);
int lens=strlen(s+1);
get_next(s,lens);
if(Next[lens]==0){
puts("Just a legend");
return 0;
}
int mx=0;
for(int i=2;i<lens;++i){
mx=max(mx,Next[i]);
}
int pos=lens;
while(pos){
pos=Next[pos];
if(pos<=mx)break;
}
if(pos==0){
puts("Just a legend");
return 0;
}
for(int i=1;i<=pos;++i){
printf("%c",s[i]);
}
return 0;
}