POJKMP算法题目解析

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]。

POJKMP算法题目解析_第1张图片
#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
题目大意是给一个字符串,从小到大输出s中既是前缀又是后缀的子串的长度。比如对于ababcababababcabab,应该输出2,4,9,18。
首先我们知道,字符串最后一个元素的next值表示最大的公共长度,而题目要我们求所有的公共长度。对于题目中的例子,除了字符串本身(18),首先能确定的是next[18]也就是9,接下来的所有解肯定都比9小。既然接下来的解是整个字符串前缀和后缀,根据整个字符串首尾的对称性,那么它也一定是next[9]也就是ababcabab的前缀和后缀。想清楚这一点,我们只需要一直这样求下去就得出所有解了。
#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
题目大意是在给定的字符矩阵中找出一个最小子矩阵,使其多次复制所得的矩阵包含原矩阵。比如 的最小子矩阵就是AB。
显然,我们要求最小的行,再求最小的列,乘起来就是结果。在求最小的行时,我们应该求出每行满足条件的所有符合题意的长度,再选取一个公共的最小值。(由于数据很弱,所以很多错误的解法也AC掉了,详见Discuss)那么我们该如何找到没一行的所有符合题意的长度呢? 只需要算一次next,如果发现后面有多余的后缀,就把后缀再拿来做一次kmp,不断重复,直到没有后缀,这个过程就可以记录所有符合题意的串了。比如aaabcaaa,先求一遍kmp,得到5,再对后缀aaa求一遍,得到1(即6,7,8),也就是说这一行满足条件的所有长度就是5,6,7,8。又比如aabaabaabaa,先求一遍kmp,得到3(即3,6,9),再对后缀aa求一遍,得到1(即10,11)。在这里对compute_next稍稍进行了修改,next[i]表示从Pattern[0]到Pattern[i]的最长匹配前缀的最后一个位置,那么i-next[i]就是符合题意的长度了,继续求下去即可。
#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);
    }
}




你可能感兴趣的:(POJKMP算法题目解析)