KMP的初步认识及题目分析
Posted knightero
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了KMP的初步认识及题目分析相关的知识,希望对你有一定的参考价值。
KMP算法对于像我这样的ACM菜鸡来说实在难以理解
虽然有大佬讲课,但是还是不理解
感谢下面这位大犇的博客,让我看懂了KMP(跪谢)
https://www.cnblogs.com/SYCstudio/p/7194315.html
---------------------------------------------------------------------------------------------------------------------------
下面是我对于学校题目的一些总结
KMP模板(解释太麻烦了,看上面的大佬博客就行)
1 #include <iostream> 2 #include <stdio.h> 3 #include <string> 4 using namespace std; 5 int tnext[1000010]; 6 7 void get_next(string ptr) 8 { 9 tnext[0] = -1; 10 int len = ptr.size(); 11 int j; 12 for (int i = 1; i <len; i++) 13 { 14 int j = tnext[i - 1]; 15 while (j>=0&&ptr[i] != ptr[j+1]) 16 { 17 j = tnext[j]; 18 } 19 if (ptr[i] == ptr[j+1]) j++; 20 tnext[i] = j; 21 // cout << tnext[i] << endl; 22 } 23 24 } 25 void KMP(string str, string ptr) 26 { 27 get_next(ptr); 28 for (int i = 0, j = -1; i < str.size(); i++) 29 { 30 while (j >= 0 && (j == str.size() - 1 || str[i] != ptr[j+1])) 31 { 32 j = tnext[j]; 33 } 34 if (str[i] == ptr[j + 1]) j++; 35 if (j == ptr.size() - 1) cout << i - j << endl; 36 } 37 38 } 39 int main() 40 { 41 string str, ptr;//str是长串,ptr是短串即目标串,要在str中去找ptr 42 cin >> str >> ptr; 43 KMP(str,ptr); 44 return 0; 45 }
---------------------------------------------------------------------------------------------------------------------------
HDU-4763-Theme Section
http://acm.hdu.edu.cn/showproblem.php?pid=4763
本题大意就是要求找出一串字符串的border,且该border在中间出现过
思路是先算next数组,如果next数组在全长的情况下不是-1,那么就进一步去字符串里找border
1 #include <iostream> 2 #include <stdio.h> 3 #include <string> 4 #include <cstring> 5 using namespace std; 6 int tnext[1000010]; 7 8 void get_next(string ptr) 9 { 10 tnext[0] = -1; 11 int len = ptr.size(); 12 int j; 13 for (int i = 1; i < len; i++) 14 { 15 int j = tnext[i - 1]; 16 while (j >= 0 && ptr[i] != ptr[j + 1]) 17 { 18 j = tnext[j]; 19 } 20 if (ptr[i] == ptr[j + 1]) j++; 21 tnext[i] = j; 22 // cout << tnext[i] << endl; 23 } 24 25 } 26 bool cc; 27 void KMP(string str, string ptr) 28 { 29 get_next(ptr); 30 for (int i = 0, j = -1; i < str.size(); i++) 31 { 32 while (j >= 0 && (j == str.size() - 1 || str[i] != ptr[j + 1])) 33 { 34 j = tnext[j]; 35 } 36 if (str[i] == ptr[j + 1]) j++; 37 if (j == ptr.size() - 1) 38 { 39 if (i - j != 0 && i - j != str.size() - ptr.size() + 1) cc = true; 40 } 41 } 42 43 } 44 int main() 45 { 46 int n; 47 cin >> n; 48 while (n--) 49 { 50 string str; 51 cin >> str; 52 cc = false; 53 memset(tnext, 0, sizeof(tnext)); 54 get_next(str); 55 int k = tnext[str.size() - 1]; 56 while (2 * (k + 1) + 1 > str.size()) k = tnext[k];//前缀和后缀不能接壤 57 if (k == -1) cout << "0" << endl; 58 else 59 { 60 while (k != -1)//先是从满足条件的最大border开始判断,不行的话用小一点的border,直到没有 61 { 62 string ptr(str, 0, k + 1);//截取border 63 64 KMP(str, ptr); 65 if (cc) {//找到了满足中间出现border条件的 66 cout << k + 1 << endl; break; 67 } 68 else k = tnext[k];//没找到就看下一层border 69 } 70 if(!cc) cout << 0 << endl; 71 } 72 } 73 return 0; 74 75 }
---------------------------------------------------------------------------------------------------------------------------
HDU-3336-Count the string
http://acm.hdu.edu.cn/showproblem.php?pid=3336
题目大意就是找出一个字符串的所有前缀在字符串里出现过几次
思路是:由于每个前缀自身都会出现一次,如果一个字符串长度为n,那么总共有n个前缀,所以前缀至少出现n次
之后任务就变成了前缀在其之后出现了几次
而next数组的含义是字符串长度为k时,其border的长度,如果我们遍历next数组求和,就可以达到有多少个前缀出现在了后面
1 #include <iostream> 2 #include <cstring> 3 #include<string> 4 using namespace std; 5 int pos[1000100]; 6 int num = 0; 7 void get_next(string ptr) 8 { 9 pos[0] = -1; 10 11 for (long long int i = 1,j=-1; i < ptr.size(); i++) 12 { 13 14 while (j >= 0 && ptr[i] != ptr[j + 1]) j = pos[j]; 15 if (ptr[i] == ptr[j + 1]) j++; 16 pos[i] = j; 17 18 } 19 20 } 21 22 int main() 23 { 24 ios::sync_with_stdio(false); 25 cin.tie(0); 26 cout.tie(0); 27 long long int t; 28 cin >> t; 29 while (t--) 30 { 31 long long int size; 32 string str; 33 cin >> size; 34 cin >> str; 35 num = 0; 36 get_next(str); 37 for (int i = 1; i < size; i++) 38 { 39 if (pos[i] != -1) num++; 40 num %= 10007; 41 } 42 43 cout << (num+size)%10007 << endl; 44 } 45 }
---------------------------------------------------------------------------------------------------------------------------
HDU-1358-Period
http://acm.hdu.edu.cn/showproblem.php?pid=1358
题目大意:有一串字符串,找出他的长度为多少时,是由循环节组成的
解题思路:遍历总长度,算出每个长度对应的next数组,那么周期就是len-next[k],如果长度%周期==0
说明它是由整数个周期组成的,输出长度/周期即可
1 #include <iostream> 2 #include<string> 3 using namespace std; 4 int pos[1000100]; 5 void get_pos(string str) 6 { 7 pos[0] = -1; 8 for (int i = 1; i < str.size(); i++) 9 { 10 int j = pos[i - 1]; 11 while (j >= 0 && str[i] != str[j + 1]) j = pos[j]; 12 if (str[i] == str[j + 1]) j++; 13 pos[i] = j; 14 } 15 } 16 int main() 17 { 18 int n,num=0; 19 while (cin >> n && n) 20 { 21 num++; 22 //if (num != 1) cout << endl; 23 string str; 24 cin >> str; 25 26 27 cout << "Test case #" << num << endl; 28 get_pos(str); 29 for (int i = 2; i <= str.size(); i++) 30 { 31 if (pos[i-1]!=-1&&(i % (i - pos[i - 1] - 1) == 0))//存在border是前提 32 { 33 cout << i << " " << i / (i - pos[i - 1] - 1) << endl; 34 } 35 } 36 cout << endl; 37 } 38 }
---------------------------------------------------------------------------------------------------------------------------
HDU-3746-Cyclic Nacklace
http://acm.hdu.edu.cn/showproblem.php?pid=3746
题目大意:给出一字符串,要求问要补多少个字符才能凑出一个循环节构成的字符串
思路:算出next数组,然后找出周期,周期-(长度%周期)就是需要补充的数量
考虑到已经是循环节构成的字符串要输出0,没有border的要输出自身长度
(此题cin,memset好像不能用,题目有问题)
1 #include <iostream> 2 #include<string> 3 #include<cstring> 4 #include<stdio.h> 5 using namespace std; 6 int pos[100010]; 7 char str[100010]; 8 int main() 9 { 10 11 int n; 12 scanf("%d", &n); 13 getchar(); 14 while (n--) 15 { 16 gets(str); 17 pos[0] = -1; 18 int len = strlen(str); 19 for (int i = 1; i < len; i++) 20 { 21 int j = pos[i - 1]; 22 while (j >= 0 && str[i] != str[j + 1]) j = pos[j]; 23 if (str[i] == str[j + 1])j++; 24 pos[i] = j; 25 } 26 27 int cyl = len - (pos[len - 1] + 1); 28 int t = (cyl - (len % cyl)) % cyl; 29 if (t == 0 && len / cyl == 1) t += len; 30 31 printf("%d ", t); 32 } 33 }
以上是关于KMP的初步认识及题目分析的主要内容,如果未能解决你的问题,请参考以下文章