KMP算法的经典例题(poj 3461、poj 2752、poj 2406、poj1961)
2016-08-03 23:48
316 查看
传送门:POJ-3461
最简单的KMP题,找出第一个字符串在第二个字符串中出现次数。
思路:其实是next数组的使用
下面给出描述: (i>1)[下标从0开始]
next[i]的意义就是:前面长度为i的字串的【前缀和后缀的最大匹配长度】
那么这题怎么利用这个性质呢?
详细分析一下:【就用上面的第一个例子说明吧】
len2 = 18 next[len2] = 9
说明对于前面长度为18的字符串,【长度为9的前缀】和【长度为9的后缀】是匹配的, 即上图的蓝色跟红色匹配
也就是整个串的最大前后缀匹配长度就是9了
所以接下来根本不需要考虑长度大于9的情况啦
好了!既然现在只需考虑长度小于9的前后缀匹配情况,那么
[问题就转化成蓝色串的前缀跟红色串的后缀的匹配问题了!!!
又因为蓝串==红串
所以问题又转化成
找蓝串自己的前缀跟自己的后缀的最大匹配了!!!
那么我们现在就要找next[9]的值了【next[9]的含义:蓝串的最大前后缀匹配】
回忆第一步:我们找的是next[len2]=9【len2=18】
怎么使得第二部目标变成9【求next[9]】呢?
其实next[len2]=9同时可以表示为:最大匹配前后缀的【前缀长度】
那么next[9]的意义就是:
【主串】的最大匹配前后缀的【前缀】的【最大匹配前后缀】了!!
也就是上面蓝串的前后缀最大匹配长度了!!
那么算法描述就是:
第一步:求next[len2], 即next[18] = 9;
第二步:把9代进来,即求next[9] = 4;
第三步:把4代进来,即求next[4] = 2;
第四步:next[2] = 0; 也就是下标2之前的串已经没有前后缀可以匹配了
所以答案就是: 2 4 9 18 【PS: 从小到大输出,18是串长,显然符合题意】
思路:KMP,next表示模式串如果第i位(设str[0]为第0位)与文本串第j位不匹配则要回到第next[i]位继续与文本串第j位匹配。则模式串第1位到next
与模式串第n-next
位到n位是匹配的。所以思路和上面一样,如果n%(n-next
)==0,则存在重复连续子串,长度为n-next
。
例如:a b a b a b
next:-1 0 0 1 2 3 4
next
==4,代表着,前缀abab与后缀abab相等的最长长度,这说明,ab这两个字母为一个循环节,长度=n-next
;
传送门:POJ-1961 Period
题意:
给你一个字符串,求这个字符串到第i个字符为止的循环节的次数。
比如aabaabaabaab,长度为12.到第二个a时,a出现2次,输出2.到第二个b时,aab出现了2次,输出2.到第三个b时,aab出现3次,输出3.到第四个b时,aab出现4次,输出4.
思路:
这道题就是上题的加强版而已。上一道题输出一个字符串的循环节出现的次数,这个是到第i个字符为止,其实就是多了一层循环。把这个字符串遍历一次即可,具体思路也以参考小白书的例题。
最简单的KMP题,找出第一个字符串在第二个字符串中出现次数。
#include <iostream> #include <cstdio> #include <cstring> #define Memset(x, a) memset(x, a, sizeof(x)) using namespace std; const int N=1e6+10; char w ,t ; int next ; int sum; void getNext(const char P[],int next[]){ int m=strlen(P); int i=0,j; j=next[0]=-1; while(i<m){ while(-1!=j && P[i]!=P[j])j=next[j]; next[++i]=++j; } } void kmp(const char T[],const char P[],int next[]){ int n=strlen(T),m=strlen(P); int i,j; getNext(P,next); i=j=0; while(i<n){ while(-1!=j && T[i]!=P[j])j=next[j]; i++;j++; if(j>=m){ sum++; j=next[j];//这儿修改很重要,不然会超时 } } } int main(){ int T; scanf("%d",&T); while(T--){ sum=0; Memset(next,0); scanf("%s%s",w,t); kmp(t,w,next); printf("%d\n",sum); } return 0; }传送门:POJ-2752
思路:其实是next数组的使用
下面给出描述: (i>1)[下标从0开始]
next[i]的意义就是:前面长度为i的字串的【前缀和后缀的最大匹配长度】
那么这题怎么利用这个性质呢?
详细分析一下:【就用上面的第一个例子说明吧】
求出next值:[非修正] |
下标: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
串: a b a b c a b a b a b a b c a b a b |
next值: -1 0 0 1 2 0 1 2 3 4 3 4 3 4 5 6 7 8 9 |
说明对于前面长度为18的字符串,【长度为9的前缀】和【长度为9的后缀】是匹配的, 即上图的蓝色跟红色匹配
也就是整个串的最大前后缀匹配长度就是9了
所以接下来根本不需要考虑长度大于9的情况啦
好了!既然现在只需考虑长度小于9的前后缀匹配情况,那么
[问题就转化成蓝色串的前缀跟红色串的后缀的匹配问题了!!!
又因为蓝串==红串
所以问题又转化成
找蓝串自己的前缀跟自己的后缀的最大匹配了!!!
那么我们现在就要找next[9]的值了【next[9]的含义:蓝串的最大前后缀匹配】
回忆第一步:我们找的是next[len2]=9【len2=18】
怎么使得第二部目标变成9【求next[9]】呢?
其实next[len2]=9同时可以表示为:最大匹配前后缀的【前缀长度】
那么next[9]的意义就是:
【主串】的最大匹配前后缀的【前缀】的【最大匹配前后缀】了!!
也就是上面蓝串的前后缀最大匹配长度了!!
那么算法描述就是:
第一步:求next[len2], 即next[18] = 9;
第二步:把9代进来,即求next[9] = 4;
第三步:把4代进来,即求next[4] = 2;
第四步:next[2] = 0; 也就是下标2之前的串已经没有前后缀可以匹配了
所以答案就是: 2 4 9 18 【PS: 从小到大输出,18是串长,显然符合题意】
#include <iostream> #include <cstdio> #include <cstring> #define Memset(x, a) memset(x, a, sizeof(x)) using namespace std; const int N=4e5+10; int next ,ans ; char s ; void getNext(const char P[],int next[]){ int m=strlen(P); int i=0,j; j=next[0]=-1; while(i<m){ while(-1!=j && P[i]!=P[j])j=next[j]; next[++i]=++j; } } int main(){ while(~scanf("%s",s)){ Memset(next,0); getNext(s,next); int cnt=0; int len=strlen(s); int j=next[len]; while(j>0){ ans[++cnt]=j; j=next[j]; } for(int i=cnt; i>0; i--)printf("%d ",ans[i]); printf("%d\n",len); } return 0; }传送门:POJ-2406 Power Strings
思路:KMP,next表示模式串如果第i位(设str[0]为第0位)与文本串第j位不匹配则要回到第next[i]位继续与文本串第j位匹配。则模式串第1位到next
与模式串第n-next
位到n位是匹配的。所以思路和上面一样,如果n%(n-next
)==0,则存在重复连续子串,长度为n-next
。
例如:a b a b a b
next:-1 0 0 1 2 3 4
next
==4,代表着,前缀abab与后缀abab相等的最长长度,这说明,ab这两个字母为一个循环节,长度=n-next
;
#include <iostream> #include <cstdio> #include <cstring> #define Memset(x, a) memset(x, a, sizeof(x)) using namespace std; const int N=1e6+10; int next ; char s ; void getNext(const char P[],int next[]){ int m=strlen(P); int i=0,j; j=next[0]=-1; while(i<m){ while(-1!=j && P[i]!=P[j])j=next[j]; next[++i]=++j; } } int main(){ while(~scanf("%s",s)){ if(s[0]=='.')break; Memset(next,0); getNext(s,next); int len=strlen(s); if(len%(len-next[len])==0)printf("%d\n",len/(len-next[len])); else printf("1\n"); } return 0; }
传送门:POJ-1961 Period
题意:
给你一个字符串,求这个字符串到第i个字符为止的循环节的次数。
比如aabaabaabaab,长度为12.到第二个a时,a出现2次,输出2.到第二个b时,aab出现了2次,输出2.到第三个b时,aab出现3次,输出3.到第四个b时,aab出现4次,输出4.
思路:
这道题就是上题的加强版而已。上一道题输出一个字符串的循环节出现的次数,这个是到第i个字符为止,其实就是多了一层循环。把这个字符串遍历一次即可,具体思路也以参考小白书的例题。
#include <iostream> #include <cstdio> #include <cstring> #define Memset(x, a) memset(x, a, sizeof(x)) using namespace std; const int N=1e6+10; char s ; int next ; int n; void getNext(const char P[],int next[]){ int m=strlen(P); int i=0,j; j=next[0]=-1; while(i<m){ while(-1!=j && P[i]!=P[j])j=next[j]; next[++i]=++j; } } int main(){ int kase=0; while(~scanf("%d",&n)&&n){ scanf("%s",s); Memset(next,0); getNext(s,next); printf("Test case #%d\n",++kase); for(int i=2; i<=n; i++){ if(next[i]>0&&i%(i-next[i])==0)printf("%d %d\n",i,i/(i-next[i])); } printf("\n"); } return 0; }
相关文章推荐
- KMP算法的练习题(poj 3461、poj 2752、poj 2406、poj 1961)
- KMP入门题 Hdu 1711 2594 3746 HUST 1010 Poj 3461 2752 2406 1961 FZU 1901
- POJ-2752 Seek the Name, Seek the Fame 字符串问题 KMP算法 求前后缀串相同数木
- poj 2406 Power Strings(kmp算法)
- poj 2406:Power Strings(KMP算法,next[]数组的理解)
- POJ 3461 Oulipo(——KMP算法)
- POJ 3461 Oulipo(字符串匹配,KMP算法)
- POJ2752 Seek the Name, Seek the Fame KMP算法
- poj 3461 Oulipo 字符串匹配 KMP算法
- POJ 3461 Oulipo KMP算法题解
- poj 3461(kmp算法)
- POJ 2752 Seek the Name, Seek the Fame kmp算法
- poj 3461 kmp算法
- poj 2406 kmp算法巩固之next数组的再理解
- poj--3461 Oulipo -------KMP算法
- POJ 2406--Power Strings(kmp算法)
- Poj-3669 Meteor Shower--Bfs经典例题
- POJ 3903 Stock Exchange_LIS(最长递增子序列) 经典例题!
- (串的模式匹配4.6.2)POJ 3461 Oulipo(KMP算法的应用——求一个单词在一行文本中的出现次数)
- POJ 3461 Oulipo KMP算法