KMP中next数组的理解与应用

迷南。 2023-08-17 15:47 245阅读 0赞

理解

1、next数组一直往前走

next数组一直往前走,得到的所有前缀也是当前主串的后缀,当然了,也是当前主串的前缀。1365470-20190131122459513-150838350.png

2、周期性字符串

1、周期性字符串$\Leftrightarrow n \,\% \, (n-next[n]) == 0 \ \&\& \ next[n] {\ } {\!}!{=} \ 0 $,循环节长度是$n-next[n]$。

1365470-20190131124532356-226085080.png

2、next数组往前跳的步长是一样的,除了最后一次。即$i-next[i]$保持恒定。

1365470-20190131125339990-1474978713.png

应用

  • 题目一:Period

思路:先求出next数组,然后遍历一遍next数组得到所有字符结尾的字符串循环节的长度及个数

代码:

ContractedBlock.gif ExpandedBlockStart.gif

  1. 1 #include<cstdio>
  2. 2 #include<cstring>
  3. 3 #include<iostream>
  4. 4 using namespace std;
  5. 5
  6. 6 const int maxn = 1000000 + 10;
  7. 7 int nexts[maxn],n;
  8. 8 char s[maxn];
  9. 9
  10. 10 void pre_kmp()
  11. 11 {
  12. 12 int i = 0, j = nexts[0] = -1;
  13. 13 while (i < n)
  14. 14 {
  15. 15 while (j != -1 && s[i] != s[j]) j = nexts[j]; //当前不匹配,j回退,寻找是否存在一个长度较小的字串和开头的字串相等
  16. 16 nexts[++i] = ++j; //j等于已匹配的长度,如果当前位置也匹配,则nexts直接为j+1
  17. 17 }
  18. 18 }
  19. 19
  20. 20 void slove()
  21. 21 {
  22. 22 pre_kmp();
  23. 23 for(int i = 2; i <= n; i++)
  24. 24 if (i % (i - nexts[i]) == 0 && nexts[i] != 0) printf("%d %d\n", i, i / (i - nexts[i]));
  25. 25 }
  26. 26
  27. 27 int main()
  28. 28 {
  29. 29 int T = 0;
  30. 30 while (scanf("%d",&n) == 1 && n)
  31. 31 {
  32. 32 scanf("%s", s);
  33. 33 s[n] = '#';
  34. 34 if (T) printf("\n");
  35. 35 printf("Test case #%d\n", ++T);
  36. 36 slove();
  37. 37 }
  38. 38 return 0;
  39. 39 }
  • 题目二:Power Strings

思路:先求next数组,再直接求循环节

代码:

ContractedBlock.gif ExpandedBlockStart.gif

  1. 1 #include<cstdio>
  2. 2 #include<cstring>
  3. 3 #include<iostream>
  4. 4 using namespace std;
  5. 5
  6. 6 const int maxn = 1000000 + 10;
  7. 7 int nexts[maxn],n;
  8. 8 char s[maxn];
  9. 9
  10. 10 void pre_kmp()
  11. 11 {
  12. 12 int i = 0, j = nexts[0] = -1;
  13. 13 //int n = strlen(s);
  14. 14 while (i < n)
  15. 15 {
  16. 16 while (j != -1 && s[i] != s[j]) j = nexts[j]; //当前不匹配,j回退,寻找是否存在一个长度较小的字串和开头的字串相等
  17. 17 nexts[++i] = ++j; //j等于已匹配的长度,如果当前位置也匹配,则nexts直接为j+1
  18. 18 }
  19. 19 }
  20. 20
  21. 21 void slove()
  22. 22 {
  23. 23 pre_kmp();
  24. 24 if (n % (n - nexts[n]) != 0) printf("1\n");
  25. 25 else printf("%d\n", n / (n - nexts[n]));
  26. 26 }
  27. 27
  28. 28 int main()
  29. 29 {
  30. 30 int T = 0;
  31. 31 while (scanf("%s", s) == 1 && s[0] != '.')
  32. 32 {
  33. 33 n = strlen(s);
  34. 34 s[n] = '#';
  35. 35 slove();
  36. 36 }
  37. 37 return 0;
  38. 38 }
  • 题目三:Count The String

思路:求所有前缀出现的次数和,由于next数组回退得到的前缀也是主串的后缀,所以所有next回退的次数加上本身的一次取模就是答案。

代码:

ContractedBlock.gif ExpandedBlockStart.gif

  1. 1 #include<cstdio>
  2. 2 #include<cstring>
  3. 3 #include<iostream>
  4. 4 using namespace std;
  5. 5
  6. 6 const int mod = 10007;
  7. 7 const int maxn = 200000 + 10;
  8. 8 int nexts[maxn], n;
  9. 9 char s[maxn];
  10. 10
  11. 11 void pre_kmp()
  12. 12 {
  13. 13 int i = 0, j = nexts[0] = -1;
  14. 14 //int n = strlen(s);
  15. 15 while (i < n)
  16. 16 {
  17. 17 while (j != -1 && s[i] != s[j]) j = nexts[j]; //当前不匹配,j回退,寻找是否存在一个长度较小的字串和开头的字串相等
  18. 18 nexts[++i] = ++j; //j等于已匹配的长度,如果当前位置也匹配,则nexts直接为j+1
  19. 19 }
  20. 20 }
  21. 21
  22. 22 void slove()
  23. 23 {
  24. 24 int ans = n;
  25. 25 pre_kmp();
  26. 26 for (int i = n; i > 0; i--)
  27. 27 {
  28. 28 int k = nexts[i];
  29. 29 while (k > 0)
  30. 30 {
  31. 31 ans = (ans + 1) % mod;
  32. 32 k = nexts[k];
  33. 33 }
  34. 34 }
  35. 35 printf("%d\n", ans);
  36. 36 }
  37. 37
  38. 38 int main()
  39. 39 {
  40. 40 int T;
  41. 41 scanf("%d", &T);
  42. 42 while (T--)
  43. 43 {
  44. 44 scanf("%d", &n);
  45. 45 scanf("%s", s);
  46. 46 slove();
  47. 47 }
  48. 48 return 0;
  49. 49 }
  • 题目四:Cyclic Nacklace

思路:题目大意:问至少添加多少个字符,使得这个字符串有至少两个循环节。若本身有两个循环节返回0,否则补充至两个循环节。

代码:

ContractedBlock.gif ExpandedBlockStart.gif

  1. 1 #include<cstdio>
  2. 2 #include<cstring>
  3. 3 #include<iostream>
  4. 4 using namespace std;
  5. 5
  6. 6 const int mod = 10007;
  7. 7 const int maxn = 200000 + 10;
  8. 8 int nexts[maxn], n;
  9. 9 char s[maxn];
  10. 10
  11. 11 void pre_kmp()
  12. 12 {
  13. 13 int i = 0, j = nexts[0] = -1;
  14. 14 n = strlen(s);
  15. 15 while (i < n)
  16. 16 {
  17. 17 while (j != -1 && s[i] != s[j]) j = nexts[j]; //当前不匹配,j回退,寻找是否存在一个长度较小的字串和开头的字串相等
  18. 18 nexts[++i] = ++j; //j等于已匹配的长度,如果当前位置也匹配,则nexts直接为j+1
  19. 19 }
  20. 20 }
  21. 21
  22. 22 void slove()
  23. 23 {
  24. 24 pre_kmp();
  25. 25 int len = n - nexts[n];
  26. 26 if (n % len == 0 && nexts[n] != 0) printf("0\n");
  27. 27 else printf("%d\n", len - nexts[n] % len);
  28. 28 }
  29. 29
  30. 30 int main()
  31. 31 {
  32. 32 int T;
  33. 33 scanf("%d", &T);
  34. 34 while (T--)
  35. 35 {
  36. 36 scanf("%s", s);
  37. 37 slove();
  38. 38 }
  39. 39 return 0;
  40. 40 }

参考链接:

1、https://vjudge.net/contest/278481#overview

2、https://blog.csdn.net/hkh746392783/article/details/52015293

转载于:https://www.cnblogs.com/lfri/p/10341479.html

发表评论

表情:
评论列表 (有 0 条评论,245人围观)

还没有评论,来说两句吧...

相关阅读

    相关 POJ 2752(Next应用)

    [传送门][Link 1] 题意:给你一个字符串,要求你从小到大输出所有字符串中满足既是该字符串的前缀又是该字符串的后缀的子串的长度。 比如: "alala"的前缀分别为\