kmp专题 模板

next数组模板: 

1. void GetNext(char* p,int next[])    

2. {    

3.     int pLen = strlen(p);    

4.     next[0] = -1;    

5.     int k = -1;    

6.     int j = 0;    

7.     while (j < pLen - 1)    

8.     {    

9.         //p[k]表示前缀,p[j]表示后缀    

10.         if (k == -1 || p[j] == p[k])     

11.         {    

12.             ++k;    

13.             ++j;    

14.             next[j] = k;    

15.         }    

16.         else     

17.         {    

18.             k = next[k];    

19.         }    

20.     }    

21. }    

 

KMP最小循环节、循环周期:

定理:假设S的长度为len,则S存在最小循环节,循环节的长度Llen-next[len],子串为S[0…len-next[len]-1]

1)如果len可以被len - next[len]整除,则表明字符串S可以完全由循环节循环组成,循环周期T=len/L

2)如果不能,说明还需要再添加几个字母才能补全。需要补的个数是循环个数L-len%L=L-(len-L)%L=L-next[len]%LL=len-next[len]

 

注意:这里的next数组模板要改成while( j < plen)因为要用到整个字符串的next值。

优化后的next数组模板: 

1. void GetNextval(char* p, int next[])    

2. {    

3.     int pLen = strlen(p);    

4.     next[0] = -1;    

5.     int k = -1;    

6.     int j = 0;    

7.     while (j < pLen - 1)    

8.     {    

9.         //p[k]表示前缀,p[j]表示后缀      

10.         if (k == -1 || p[j] == p[k])    

11.         {    

12.             ++j;    

13.             ++k;    

14.             //较之前next数组求法,改动在下面4    

15.             if (p[j] != p[k])    

16.                 next[j] = k;   //之前只有这一行    

17.             else    

18.                 //因为不能出现p[j] = p[ next[j ]],所以当出现时需要继续递归,k = next[k] = next[next[k]]    

19.                 next[j] = next[k];    

20.         }    

21.         else    

22.         {    

23.             k = next[k];    

24.         }    

25.     }    

26. }   


 

 

 

 

kmp模板:

 

1. int KmpSearch(char* s, char* p)    

2. {    

3.     int i = 0;    

4.     int j = 0;    

5.     int sLen = strlen(s);    

6.     int pLen = strlen(p);    

7.     while (i < sLen && j < pLen)    

8.     {    

9.         //①如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++j++        

10.         if (j == -1 || s[i] == p[j])    

11.         {    

12.             i++;    

13.             j++;    

14.         }    

15.         else    

16.         {    

17.             //②如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j]        

18.             //next[j]即为j所对应的next          

19.             j = next[j];    

20.         }    

21.     }    

22.     if (j == pLen)    

23.         return i - j;    

24.     else    

25.         return -1;    

26. }    

 

 

 

 

题型:

二:变形题:寻找字串的个数(可重复)
Sample Input

3

BAPC

BAPC

AZA

AZAZAZA

VERDI

AVERDXIVYERDIAN

Sample Output

1

3

0

 题意:每组输入两个字符串,判断第一个字符串在第二个字符串中出现了几次,这个题里的next数组要计算最后一个,即while(j < plen),可以对比下一道题理解一下。

 代码:         

1. #include<iostream>  

2. #include<cstdio>  

3. using namespace std;    

4. char text[1000010];  

5. char partten[10010];  

6. int nex[10010];  

7.   

8. void GetNextval(char *p)  

9. {  

10.     int pLen = strlen(p);  

11.     nex[0] = -1;  

12.     int k = -1;  

13.     int j = 0;  

14.     while (j < pLen)//因为这个题要计算共有多少个,所有需要知道最后一个字符的next值,所以这里的j要计算到最后一个数  

15.   

16.     {  

17.         if (k == -1 || p[j] == p[k])  

18.         {  

19.             ++j;  

20.             ++k;  

21.             if (p[j] != p[k])  

22.                 nex[j] = k;  

23.             else  

24.                 nex[j] = nex[k];  

25.         }  

26.         else  

27.         {  

28.             k = nex[k];  

29.         }  

30.     }  

31. }  

32.   

33. int KmpSearch(char *s,char *p)  

34. {  

35.   

36.     int i = 0;  

37.     int j = 0;  

38.     int flag = 0;  

39.     int sLen = strlen(s);  

40.     int pLen = strlen(p);  

41.     while (i < sLen && j <=pLen)  

42.     {  

43.         if (j == -1 || s[i] == p[j])  

44.         {  

45.             i++;  

46.             j++;  

47.         }  

48.         else  

49.         {  

50.             j = nex[j];  

51.         }  

52.         if(j==pLen)  

53.             flag++;  

54.     }  

55.     return flag;  

56. }  

57.   

58.   

59. int main()  

60. {  

61.     int t,num;  

62.     scanf("%d",&t);  

63.     while(t--)  

64.     {  

65.         cin >> partten >> text;  

66.         GetNextval(partten);  

67.         num = KmpSearch(text,partten);  

68.         printf("%d\n",num);  

69.   

70.   

71.     }  

72.     return 0;  

73. }  


三:变形题:寻找子串的个数(一直往下走不可回溯)
Sample Input

abcde a3

aaaaaa  aa

#

Sample Output

0

3

这个题的next数组不需要计算出最后一个,即while(j < plen - 1)即可。 

代码: 

1. #include<iostream>  

2. #include<cstdio>  

3. using namespace std;    

4. char text[1000010];  

5. char partten[10010];  

6. int nex[10010];   

7. void GetNextval(char *p)  

8. {  

9.     int pLen = strlen(p);  

10.     nex[0] = -1;  

11.     int k = -1;  

12.     int j = 0;  

13.     while (j < pLen -1)  

14.   

15.     {  

16.         if (k == -1 || p[j] == p[k])  

17.         {  

18.             ++j;  

19.             ++k;  

20.             if (p[j] != p[k])  

21.                 nex[j] = k;     

22.             else  

23.                 nex[j] = nex[k];  

24.         }  

25.         else  

26.         {  

27.             k = nex[k];  

28.         }  

29.     }  

30. }  

31.   

32.   

33. int KmpSearch(char *s,char *p)  

34. {  

35.   

36.     int i = 0;  

37.     int j = 0;  

38.     int flag = 0;  

39.     int sLen = strlen(s);  

40.     int pLen = strlen(p);  

41.     while (i < sLen && j <=pLen)  

42.     {  

43.         if (j == -1 || s[i] == p[j])  

44.         {  

45.             i++;  

46.             j++;  

47.         }  

48.         else  

49.         {  

50.             j = nex[j];  

51.         }  

52.         if(j==pLen)  

53.             flag++;  

54.     }  

55.     return flag;  

56. }  

57.   

58.   

59. int main()  

60. {  

61.     int num;  

62.     while(cin >> text && text[0]!='#')  

63.     {  

64.         cin >> partten;  

65.         GetNextval(partten);  

66.         num = KmpSearch(text,partten);  

67.         printf("%d\n",num);  

68.   

69.   

70.     }  

71.     return 0;  

72. }  

四:变形题:字符串周期问题

KMP最小循环节、循环周期

定理:假设S的长度为len,则S存在最小循环节,循环节的长度Llen-next[len],子串为S[0…len-next[len]-1]

1)如果len可以被len - next[len]整除,则表明字符串S可以完全由循环节循环组成,循环周期T=len/L

2)如果不能,说明还需要再添加几个字母才能补全。需要补的个数是循环个数L-len%L=L-(len-L)%L=L-next[len]%LL=len-next[len]

注意:这里的next数组模板要改成whilej<plen)因为要用到整个字符串的next

Input

3

aaa

abca

abcde

Sample Output

0

2

5

 

题意:每个字母代表一种珍珠,要求补最少的珠子使珍珠串成为循环串

思路:转化为求最小循环字串即可,套用上面的公式:

代码: 

1. #include <iostream>  

2. #include <cstdio>  

3. #include <cstring>  

4.   

5. using namespace std;  

6.   

7. char partten[100000+10];  

8. int nex[100000+10];  

9. int pLen;  

10.   

11. void GetNext()  

12. {  

13.     nex[0] = -1;  

14.     int k = -1;  

15.     int j = 0;  

16.     while (j < pLen )  

17.     {  

18.         //p[k]表示前缀,p[j]表示后缀  

19.         if (k == -1 || partten[j] == partten[k])  

20.         {  

21.             ++k;  

22.             ++j;  

23.             nex[j] = k;  

24.         }  

25.         else  

26.         {  

27.             k = nex[k];  

28.         }  

29.     }  

30. }  

31.   

32. int main()  

33. {  

34.     int t;  

35.     cin >> t;  

36.     while(t--)  

37.     {  

38.         scanf("%s",partten);  

39.         pLen = strlen(partten);  

40.         GetNext();  

41.         int circle_len = pLen - nex[pLen];//代表循环节的长度  

42.         if(circle_len != pLen && pLen%circle_len==0)//如果可以多次循环  

43.                 printf("0\n");  

44.         else  

45.             printf("%d\n",circle_len - nex[pLen]%circle_len);//取余的作用:abcab,去掉abc  

46.     }                     //循环节的长度减去已经匹配的长度  

47.     return 0;  

48. }  

Sample Input

3

aaa

12

aabaabaabaab

0

Sample Output

Test case #1

2 2

3 3

 

Test case #2

2 2

6 2

9 3

12 4

 

题意:给出一个字符串,找出由循环子字符串前缀,输出前缀长度及其中相同的子字符串数(即此前缀中循环子串的个数)

思路:这个相当于遍历每一个字母时都判断一下有没有循环字串,可以在求next数组的时候一起完成

代码: 

1. #include <iostream>  

2. #include <cstdio>  

3. #include <cstring>  

4. using namespace std;  

5.   

6. char p[1000000+10];  

7. int nex[1000000+10]; 

8. void Getnext()  

9. {  

10.     int plen = strlen(p),circle_len;  

11.     int k = -1 ;  

12.     int j = 0 ;//现在的字符 -  

13.     nex[0] = -1;  

14.     while( j < plen)  

15.     {  

16.         if(k == -1 || p[k]==p[j])  

17.         {  

18.             ++j;  

19.             ++k;  

20.             nex[j] = k;  

21.             circle_len = j - nex[j];//nex[j]记录的时上一个字符的最大公共前后缀,而j恰好是正常顺序中的次序  

22.             if(nex[j] > 0 && j % circle_len == 0)//nex[j]>0代表有前后缀,并且前后缀循环  

23.                 printf("%d %d\n",j,j/circle_len);  

24.   

25.         }  

26.         else  

27.             k = nex[k];  

28.     }  

29. }  

30.   

31.   

32. int main()  

33. {  

34.     int t,i = 1;  

35.     while(scanf("%d",&t)!=EOF && t)  

36.     {  

37.         scanf("%s",p);  

38.         printf("Test case #%d\n",i++);  

39.         Getnext();  

40.         printf("\n");  

41.     }  

42.     return 0;  

43. }                                                                     

 

Sample Input

bcabcab

efgabcdefgabcde

Sample Output

3

7

题目大意:

有一个字符串A,假设A是“abcdefg”,  由A可以重复组成无线长度的AAAAAAA,即“abcdefgabcdefgabcdefg.....”.

从其中截取一段abcdefgabcdefgabcdefgabcdefg”,取红色部分为截取部分,设它为字符串B。

现在先给出字符串B, 求A最短的长度。

 

分析与总结:

设字符串C = AAAAAAAA....  由于C是由无数个A组成的,所以里面有无数个循环的A, 那么从C中的任意一个起点开始,也都可以有一个循环,且这个循环长度和原来的A一样。(就像一个圆圈,从任意一点开始走都能走回原点)。

所以,把字符串B就看成是B[0]为起点的一个字符串,原问题可以转换为:求字符串B的最小循环节

根据最小循环节点的求法,很容易就可以求出这题。

代码: 

1. #include <iostream>  

2. #include <cstdio>  

3. #include <cstring>  

4.   

5. using namespace std;  

6.   

7. char s[1000000+10];  

8. int nex[1000000+10];  

9.   

10. void getnext()  

11. {  

12.     int plen = strlen(s);  

13.     int k = -1;  

14.     int j = 0;  

15.     nex[0] = -1;  

16.     while( j < plen)  

17.     {  

18.         if(k == -1 || s[j]==s[k])  

19.         {  

20.             j++;  

21.             k++;  

22.             nex[j] = k;  

23.         }  

24.         else  

25.             k = nex[k];  

26.     }  

27.     printf("%d\n",plen - nex[plen]);  

28.   

29.   

30. }  

31.   

32.   

33. int main()  

34. {  

35.     while(scanf("%s",s)!=EOF)  

36.         getnext();  

37.     return 0;  

38. }  

                                                                               

五:对next数组的理解

Sample Input

ababcababababcabab

aaaaa

Sample Output

2 4 9 18

1 2 3 4 5


题意:求所有匹配字符串前缀后缀的长度

思路:其实next数组就表示的时最长的前缀和后缀匹配,那么只要next数组的值不为零的话,就代表有前后缀匹配,一直递归下去,注意整个字符串也符合条件。所以求出最终的next数组一直递归就可以了。

代码:

1. #include <iostream>  

2. #include <cstring>  

3. #include <cstdio>  

4.   

5. using namespace std;  

6.   

7. char p[400000+10];  

8. int nex[400000+10];  

9. int plen ;  

10. void getnext()  

11. {  

12.     plen = strlen(p);  

13.     int k = -1;  

14.     int j = 0;  

15.     nex[0] = -1;  

16.     while( j < plen )  

17.     {  

18.         if( k == -1 || p[j] == p[k])  

19.         {  

20.             j++;  

21.             k++;  

22.             nex[j] = k;  

23.         }  

24.         else  

25.             k = nex[k];  

26.     }  

27.   

28. }  

29.   

30. int main()  

31. {  

32.     int s[40000] ,i = 0;  

33.     while(scanf("%s",p)!=EOF)  

34.     {  

35.         i = 0;  

36.         getnext();  

37.         int k = plen;//整个字符串的长度  

38.         while(nex[k] != -1)  

39.         {  

40.             s[i++] = k;//递归索引求出所有的前后缀长度  

41.             k = nex[k];  

42.         }  

43.         for(int j = --i; j >= 0; j--)//倒序输出即可  

44.             printf("%d ",s[j]);  

45.         printf("\n");  

46.     }  

47.     return 0;  

48. }  

 

                                                          

Sample Input

1

4

abab

Sample Output

6

     

题意:求串的前缀在串中出现的次数 

思路:KMPnext[]数组的应用,处理完next[]数组后,则以第i个字母为结尾的串中出现前缀的个数就是本身加上dp[next[i]]的结果,因为我们知道next[i]数组代表的是 和前缀匹配的长度,所以可以归纳到前缀中

 

代码: 

1. #include <iostream>  

2. #include <cstdio>  

3. #include <cstring>  

4.   

5. using namespace std;  

6.   

7. char s[200005];  

8. int nex[200005];  

9. int dp[200005];  

10. void getnext()  

11. {  

12.     int slen = strlen(s);  

13.     int k = -1;  

14.     int j = 0;  

15.     nex[0] = -1;  

16.     while(j < slen)  

17.     {  

18.         if(k == -1 || s[k]==s[j])  

19.             nex[++j] = ++k;  

20.         else  

21.             k=nex[k];  

22.     }  

23. }  

24.   

25. int main()  

26. {  

27.     int t,n;  

28.     cin >> t;  

29.     while(t--)  

30.     {  

31.         cin >> n;  

32.         scanf("%s",s);  

33.         getnext();  

34.         int slen = strlen(s);  

35.         memset(dp,0,sizeof(dp));  

36.         int sum = 0 ;  

37.         for(int i = 1 ; i <=slen; i++)  

38.         {  

39.             dp[i] = dp[nex[i]]+1;  

40.             sum =(sum+dp[i])%10007;  

41.         }  

42.         printf("%d\n",sum);  

43.     }  

44. }  

 

六:求多个字符串的最长公共字串

 

                                                                         

Sample Input

3

2

GATACCAGATACCAGATACCAGATACCAGATACCAGATACCAGATACCAGATACCAGATA

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

3

GATACCAGATACCAGATACCAGATACCAGATACCAGATACCAGATACCAGATACCAGATA

GATACTAGATACTAGATACTAGATACTAAAGGAAAGGGAAAAGGGGAAAAAGGGGGAAAA

GATACCAGATACCAGATACCAGATACCAAAGGAAAGGGAAAAGGGGAAAAAGGGGGAAAA

3

CATCATCATCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

ACATCATCATAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

AACATCATCATTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT

Sample Output

no significant commonalities

AGATAC

CATCATCAT


 题意:给你几个DNA序列长度为60,以第一个为模板,找到之后的DNA中与模板DNA相同的子序列,且保证子序列最长(长度大于等于3)。

思路:    暴力寻找,枚举相同序列的长度以第一个DNA为模板向其他串中找。其中有个技巧性的地方就是strstr()函数的使用,strstr(a,b)函数为在a中找b,如果可以找到b那么会返回最初始找到b时的位置的地址,若找不到b则返回NULL。

 

代码: 

1. #include <iostream>  

2. #include <cstring>  

3. #include <string>  

4.   

5. using namespace std;  

6.   

7. char tem[65];  

8. char str[15][65];  

9. char fin[65];  

10.   

11. int m;  

12.   

13. int judge()  

14. {  

15.     int i;  

16.     for(i = 1 ;i < m; i++)  

17.     {  

18.         if(strstr(str[i],tem)==0)//str[i]中是否存在tem,存在就返回所在位置 否则返回null  

19.             return 0;  

20.     }  

21.     return 1;  

22. }  

23.   

24.   

25. int main()  

26. {  

27.     int n,i,k,j;  

28.     cin >> n;  

29.     while(n--)  

30.     {  

31.         cin >> m;  

32.         for(i = 0; i < m; i++)  

33.             cin >> str[i];  

34.         k = 3;  

35.         int flag = 0;  

36.         while(k <= 60)//暴力搜索 字符串长度遍历  

37.         {  

38.             for(i = 0 ; i <= 60 - k;i++)//枚举字符串长度  

39.             {  

40.                 memset(tem,'\0',sizeof(tem));  

41.                 for( j = 0 ; j < k ; j++)  

42.                     tem[j] = str[0][i+j];//i记录字符串的起始位置  

43.                 tem[k] = '\0';  

44.                 if(judge())  

45.                 {  

46.                     flag = 1;  

47.                     strcpy(fin,tem);//找到了就记录下来  

48.                 }  

49.             }  

50.             k++;  

51.         }  

52.         if(flag==1)  

53.             cout<<fin<<endl;  

54.         else  

55.             cout<<"no significant commonalities"<<endl;  

56.   

57.     }  

58.     return 0;  

59. }  


七:求a串前缀和b串后缀最长公共字串

                                           

Sample Input

clinton

homer

riemann

marjorie

Sample Output

0

rie 3

 

题意给你a,b两个串,求出a串的前缀与b串后缀最长的公共串。

思路:由于是求b的后缀最长是a的前缀。不妨将a,b连接起来,求next数组,这样next[len](len表示a,b连接后的长度)则可以表示匹配到的最大长度。 不过需要注意的是next[len]不能大于a,b的长度。


代码: 

1. #include <iostream>  

2. #include <cstdio>  

3. #include <cstring>  

4. using namespace std;  

5.   

6. char s1[100005];  

7. char s2[50005];  

8. int nex[100005];  

9. int len1,len2,len;  

10.   

11. void getnext()  

12. {  

13.     int k = -1;  

14.     int j = 0;  

15.     nex[0] = -1;  

16.     while(j<len)  

17.     {  

18.         if(k == -1 || s1[j]==s1[k])  

19.         {  

20.             j++;  

21.             k++;  

22.             nex[j] = k;  

23.         }  

24.         else  

25.             k = nex[k];  

26.     }  

27. }  

28.   

29.   

30. int main()  

31. {  

32.     while(~scanf("%s",s1))  

33.     {scanf("%s",s2);  

34.     len1 = strlen(s1);  

35.     len2 = strlen(s2);  

36.     strcat(s1,s2);  

37.     len = len1+len2;  

38.     getnext();  

39.     int i,j = nex[len];  

40.     while(j>len1||j>len2)  

41.         j = nex[j];  

42.     if(j == 0)  

43.         printf("0\n");  

44.     else  

45.     {  

46.         for(i = 0 ; i < j ; i++)  

47.             printf("%c",s1[i]);  

48.         printf(" %d\n",j);  

49.     }  

50.     }  

51.     return 0;  

52. }  

猜你喜欢

转载自blog.csdn.net/yihanyifan/article/details/80042357