Poj KMP 总结中给出了POJ中几乎所有KMP算法的思路。我挑选了其中几道难道适中的题目练习。如果读者还不清楚KMP算法的原理,可以看我的另外一篇文章:字符串匹配-KMP算法。
我们先给出KMP算法最基本的代码:
#include<cstdio> #include<cstring> #include<iostream> using namespace std; int Next[10010]; char Pattern[10010],Text[1000010]; int m,n; void compute_next(int *Next,char *Pattern) { int k=0; Next[0]=Next[1]=0; //Next[i]表示Pattern[0]到[i-1]的最大公共长度,k是Next[i-1]的值 for(int i=2;i<=m;i++) { for(;k!=0&&Pattern[k]!=Pattern[i-1];k=Next[k]); if(Pattern[k]==Pattern[i-1]) k++; Next[i]=k; } } int kmp_match(char *Text,char *Pattern,int *Next) { int i=0,j=0,ans=0; //i表示上一次迭代匹配了多少个字符 //j表示这次迭代从Text的哪个字符开始比较 //ans表示匹配成功的次数 while(j<n) { for(i=Next[i];i<m&&Pattern[i]==Text[j];i++,j++); //直接从Next[q]的位置开始匹配 if(i==0) j++; //如果一个也没有匹配j向后移动一位 else if(i==m) ans++; //如果匹配成功ans++ } return ans; } int main() { int N; scanf("%d",&N); while(N--) { scanf("%s %s",Pattern,Text); m=strlen(Pattern),n=strlen(Text); compute_next(Next,Pattern); cout<<kmp_match(Text,Pattern,Next)<<endl; } }
POJ1961(POJ2406)
题目大意是给一个字符串,求这个字符串到第i个字符为止的循环节的次数。比如对于aabaabaabaab,长度为12,到第二个a时,a出现2次,输出2;到第二个b时,aab出现了2次,输出2;到第三个b时,aab出现3次,输出3;到第四个b时,aab出现4次,输出4。
对于题目中的例子,最后一个b的next值为9,观察一下就会发现实际上这个值指示了最后一个循环节开始的位置。因此按照我们的定义,i-next[i]表示从第1个字符到第i个字符所能构成的最短的循环节的长度。如果i能整除i-next[i],则说明整个字符串能由重复的循环节构成。当然,i不能等于i-next[i]。
#include<cstdio> #include<iostream> using namespace std; int Next[1000010]; char Pattern[1000010]; int T=1,len,length; void compute_next(int *Next,char *Pattern) { int k=0; Next[0]=Next[1]=0; for(int i=2;i<=len;i++) { for(;k!=0&&Pattern[k]!=Pattern[i-1];k=Next[k]); if(Pattern[k]==Pattern[i-1]) k++; Next[i]=k; } } int main() { while(scanf("%d",&len)&&len) { scanf("%s",Pattern); compute_next(Next,Pattern); printf("Test case #%d\n",T++); for(int i=1;i<=len;++i) { //循环节的长度 length=i-Next[i]; //如果有多个循环 if(i!=length&&i%length==0) printf("%d %d\n",i,i/length); } printf("\n"); } }POJ2752
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; int res[400010],Next[400010]; char Pattern[400010]; int m; void compute_next(int *Next,char *Pattern) { int k=0; Next[0]=Next[1]=0; for(int i=2;i<=m;i++) { for(;k!=0&&Pattern[k]!=Pattern[i-1];k=Next[k]); if(Pattern[k]==Pattern[i-1]) k++; Next[i]=k; } } int main() { while(scanf("%s",Pattern)!=EOF) { int len=strlen(Pattern); m=len; compute_next(Next,Pattern); int i=0,j=0; while(Next[m]!=0) { res[i]=Next[m]; m=Next[m]; i++; } sort(res,res+i); for(j=0;j<i;j++) cout<<res[j]<<" "; cout<<len<<endl; } }
POJ2541
这道题题意比较绕,翻译一下就是在文本T[1…n]中找一个最长的后缀(长度为m,m<=13),以这个后缀为匹配的模式P:T[n-m+1…n],找出P在文本T[1…n-1]出现的最后一个起始位置k(k<=n-m),若找到k,则得出T[n+1]为找到的模式T[k…k+m-1]的下一个位置,即T[n+1]=T[k+m]。如果m=13时找不到,则尝试m=12直到m=1;如果都找不到T[n+1]=0。对于样例:
10 7
1101110010
1101110010
1: 11011100100
2: 110111001001
3: 1101110010010
4: 11011100100100
5: 110111001001001
6: 1101110010010010
7: 11011100100100100
本题也可以用状态压缩dp的办法,这里我们不讨论。如果直接暴力枚举字符串的后面13个的字母,然后再用KMP匹配,这样的话,就分别要匹配后面的13,12,11....1个字母。但是如果把原来的字符串逆序转换一下,只需要求一次就够了。
#include<cstdio> #include<cstring> #include<iostream> using namespace std; const int MAXN=1002000; const int START=2000; char T[MAXN]; int Next[MAXN]; int N,L; char compute_next(char *Pattern,int len) { int k=0; int pos=-1,Max=-1; Next[0]=Next[1]=0; for(int i=2;i<=len;i++) { for(;k!=0&&Pattern[k]!=Pattern[i-1];k=Next[k]); if(Pattern[k]==Pattern[i-1]) k++; Next[i]=k; if(Next[i]==13) return Pattern[i-Next[i]-1]; if(Max<Next[i]) { Max=Next[i]; pos=i-Next[i]-1; } } if(Max==-1) return '0'; return Pattern[pos]; } int main() { while(scanf("%d%d%*c",&N,&L)!=EOF) { char *p; char *str=T+START; p=str; gets(str); //逆序转换 for(int i=0,j=N-1;i<N/2;++i,--j) { char temp=str[i]; str[i]=str[j]; str[j]=temp; } for(int i=0;i<L;++i) { *(str-1)=compute_next(str,N); --str; ++N; } --p; while(p>=str) { putchar(*p); --p; } puts(""); } }POJ2185
#include<cstdio> #include<cstring> #include<iostream> using namespace std; const int N=10000+10; int Next[N],cnt[N],n,m; char Text[N][80]; void compute_next(int *Next,char *Pattern) { int m=strlen(Pattern); Next[0]=-1; for(int i=1;i<m;i++) { int j=Next[i-1]; while(j!=-1&&Pattern[j+1]!=Pattern[i]) j=Next[j]; if(Pattern[j+1]==Pattern[i]) j++; Next[i]=j; } for(int i=0;i<m;i++) cout<<Next[i]<<endl; } void solve(int r) { Next[0]=-1; for(int i=1;i<n;i++) { int j=Next[i-1]; while(j!=-1&&strcmp(Text[j+1],Text[i])) j=Next[j]; if(strcmp(Text[j+1],Text[i])==0) j++; Next[i]=j; } printf("%d\n",(n-1-Next[n-1])*(r)); } int main() { while(~scanf("%d%d",&n,&m)) { memset(cnt,0,sizeof(cnt)); for(int i=0;i<n;i++) { scanf("%s",Text[i]); compute_next(Next,Text[i]); int j=m-1; while(j!=-1) { cnt[m-1-Next[j]]++; j=Next[j]; } } int r=0; for(int i=1;i<=m;i++) { if(cnt[i]==n) { r=i; break; } } for(int i=0;i<n;i++) Text[i][r]=0; solve(r); } }