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存在最小循环节,循环节的长度L为len-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]%L,L=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存在最小循环节,循环节的长度L为len-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]%L,L=len-next[len]。
注意:这里的next数组模板要改成while(j<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
题意:求串的前缀在串中出现的次数
思路:KMP的next[]数组的应用,处理完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. }