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;
}
posted @ 2021-07-10 19:21  Qquun  阅读(38)  评论(0)    收藏  举报