kmp
注意:next数组不要用next命名(可能和关键字重复)。
简单介绍:
字符串匹配问题:字符串 s 中查找某个字符串 p 是否出现
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 
 | const int MAXN = 2e5 + 10;int n,m;
 int nxt[MAXN],f[MAXN];
 char s[MAXN],p[MAXN];
 
 
 void kmp() {
 n = strlen(s + 1);
 m = strlen(p + 1);
 int j = 0;
 nxt[1] = 0;
 for(int i = 2; i <= m; i++) {
 while(j > 0 && p[j + 1] != p[i])
 j = nxt[j];
 if(p[j + 1] == p[i])
 j++;
 nxt[i] = j;
 }
 
 j = 0;
 
 for(int i = 1; i <= n; i++) {
 while((j == m) || (j > 0 && p[j + 1] != s[i]))
 j = nxt[j];
 if(p[j + 1] == s[i])
 j++;
 f[i] = j;
 }
 }
 
 
 
 | 
时间复杂度
O(n + m)
Question 1: KMP板子题
给你两个字符串 a,b,字符串均由小写字母组成,现在问你 b 在 a 中出现了几次。
题解:很裸。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 
 | const int MAXN = 2e5 + 10;int n,m;
 int nxt[MAXN],f[MAXN];
 char s[MAXN],p[MAXN];
 
 
 void kmp() {
 
 n = strlen(s + 1);
 m = strlen(p + 1);
 int j = 0;
 nxt[1] = 0;
 for(int i = 2; i <= m; i++) {
 while(j > 0 && p[j + 1] != p[i])
 j = nxt[j];
 if(p[j + 1] == p[i])
 j++;
 nxt[i] = j;
 }
 
 j = 0;
 
 for(int i = 1; i <= n; i++) {
 while((j == m) || (j > 0 && p[j + 1] != s[i]))
 j = nxt[j];
 if(p[j + 1] == s[i])
 j++;
 f[i] = j;
 }
 
 int ans = 0;
 for(int i=1;i<=n;i++) {
 if(f[i] == m) ++ans;
 }
 if(!ans) {
 std::cout << -1 << endl;
 std::cout << -1 << endl;
 } else {
 std::cout << ans << endl;
 for(int i=1;i<=n;i++) {
 if(f[i] == m)
 std::cout << i - m + 1 << " ";
 }
 std::cout << endl;
 }
 }
 
 
 void JiuCherish(){
 std::cin >> s + 1 >> p + 1;
 kmp();
 }
 
 | 
Question 2:最小循环覆盖
给你一个字符串 a,你需要求出这个字符串的最小循环覆盖的长度。
b 是 a 的最小循环覆盖,当且仅当 a 是通过 b 复制多次并连接后得到的字符串的前缀,且 b 是满足条件的字符串中长度最小的。
输入一个字符串 a,输出一个数表示最小循环覆盖 b 的长度。
题解:
简单来说就是找任意一段字符,然后后面每一段字符和你找的字符是相同的。
妙妙结论题:找到的长度其实就是 m - nxt[m];
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 
 | int n,m;int nxt[MAXN],f[MAXN];
 char s[MAXN],p[MAXN];
 
 
 void kmp() {
 
 n = strlen(s + 1);
 m = strlen(p + 1);
 int j = 0;
 nxt[1] = 0;
 for(int i = 2; i <= m; i++) {
 while(j > 0 && p[j + 1] != p[i])
 j = nxt[j];
 if(p[j + 1] == p[i])
 j++;
 nxt[i] = j;
 }
 
 j = 0;
 
 for(int i = 1; i <= n; i++) {
 while((j == m) || (j > 0 && p[j + 1] != s[i]))
 j = nxt[j];
 if(p[j + 1] == s[i])
 j++;
 f[i] = j;
 }
 std::cout << m - nxt[m] << endl;
 }
 
 
 void JiuCherish(){
 std::cin >> p + 1;
 kmp();
 }
 
 | 
Question 3:[UVA 12467] Secret word
给你一个字符串 s,你需要找出 s 中最长的 secret word,一个字符串 p 是 secret word 需要满足:
- p 是 s 的子串(p 可以与 s 相等);
- 将 p 翻转后是 s 的前缀。
输入一行字符串 s,输出一行字符串为你求得的 p。
题解:就是讲字符串延长2倍,然后跑一个kmp,找到最大的nxt值,最后反向打印即可。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 
 | int n,m;int nxt[MAXN],f[MAXN];
 char s[MAXN],p[MAXN];
 
 
 void kmp() {
 
 n = strlen(s + 1);
 m = strlen(p + 1);
 p[m + 1] = '#';
 for(int i = m + 2,j = m; j; --j, ++i)
 p[i] = p[j];
 int j = 0;
 nxt[1] = 0;
 for(int i = 2; i <= m + m + 1; i++) {
 while(j > 0 && p[j + 1] != p[i])
 j = nxt[j];
 if(p[j + 1] == p[i])
 j++;
 nxt[i] = j;
 }
 
 j = 0;
 
 for(int i = 1; i <= n; i++) {
 while((j == m) || (j > 0 && p[j + 1] != s[i]))
 j = nxt[j];
 if(p[j + 1] == s[i])
 j++;
 f[i] = j;
 }
 int x = 0;
 for(int i=m+2;i<=m+m+1;i++) {
 x = std::max(x,nxt[i]);
 }
 for(int i = x;i>=1;i--) {
 std::cout << p[i];
 }
 }
 
 
 void JiuCherish(){
 std::cin >> p + 1;
 kmp();
 }
 
 | 
扩展KMP(Z  algorithm)
与kmp区别:两者求next数组不同,一个是到字符s[i]结束,另一个是从字符s[i]开始。
思路:
代码解读:
对于满足 i > 1 的位置 i , z[i] 表示字符串 s 和后缀 s[i]~s[n] 的最长公共前缀的长度。
如何求 z 数组呢?
首先 z[1] = 0 ,开始往后枚举每个位置,去计算。
若是算第 z[i] 的值,此时 从 1 到 i 的结果一定是算好的。
对于 j, 有 s[j] … s[j + z[j] - 1] 和 s[1] … s[z[j]] 完全相等。
为了计算 z[i] ,在枚举 i 的过程中,我们需要维护一个 R 最大的区间 [L,R]。
其中 L = j (j < i), R = j + z[j] - 1。
情况一:假如 i <= R
我们知道:s[L] 到 S[R] 一定和区间 S[1] 到 S[R - L + 1] 是一一映射的关系。
那么我们约定一个数 k = i - L + 1,那么此时在[L,R]中的位置对应了 k 在 [1,R - L + 1] 中的位置,此时 s[i] 到 S[R] 和 S[k] 到 S[R - L + 1 是一一映射的。
Case1 : 如果 z[k] < R - i + 1,那么匹配不到右边,此时只能z[i] = z[k]
Case2 : 如果 z[k] >= R - i + 1 那么 i 是能匹配到 R 的,这时从 R + 1 开始暴力向后匹配即可。
情况二:假如 i > R
那么暴力枚举匹配即可。
求出 z[i] 后 需要更新 L 和 R。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 
 | void exkmp() {int L = 1, R = 0;
 z[1] = 0;
 for(int i = 2; i <= n; i++) {
 if(i > R)  z[i] = 0;
 else  {
 int k = i - L + 1;
 z[i] = std::min(z[k],R - i + 1);
 }
 while(i + z[i] <= n && s[z[i] + 1] == s[i + z[i]]) ++z[i];
 if(i + z[i] - 1 > R)
 L = i,R = i + z[i] - 1;
 }
 }
 
 | 
时间复杂度:O(n)
Question 1: exkmp板子题(同上面的kmp板子)
给你两个字符串 a,b,字符串均由小写字母组成,现在问你 b 在 a 中出现了几次。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 
 | const int MAXN = 2e5 + 10;int n,m;
 int z[MAXN];
 char s[MAXN],p[MAXN];
 
 
 void exkmp() {
 n = strlen(s + 1);
 m = strlen(p + 1);
 p[m + 1] = '#';
 for(int i = m + 2, j = 1; j <= n; i++, j++) p[i] = s[j];
 int L = 1, R = 0;
 z[1] = 0;
 for(int i = 2; i <= n + m + 1; i++) {
 if(i > R)  z[i] = 0;
 else  {
 int k = i - L + 1;
 z[i] = std::min(z[k],R - i + 1);
 }
 while(i + z[i] <= n + m + 1 && p[z[i] + 1] == p[i + z[i]]) ++z[i];
 if(i + z[i] - 1 > R)
 L = i,R = i + z[i] - 1;
 }
 int ans = 0;
 for(int i = m + 1;i <= n + m + 1;i++) {
 if(z[i] == m) ++ans;
 }
 if(!ans)
 std::cout << -1 << endl << -1 << endl;
 else {
 std::cout << ans << endl;
 for(int i = m + 2;i <= n + m + 1;i++) {
 if(z[i] == m)
 std::cout << i - m - 1 << " ";
 }
 std::cout << endl;
 }
 }
 
 void JiuCherish(){
 std::cin >> s + 1 >> p + 1;
 exkmp();
 }
 
 | 
Question 2:[CF 126B] Password
给你一个字符串 s,由小写字母组成,你需要求出其中最长的一个子串 p,满足 p 既是 s 的前缀,又是 s 的后缀,且在 s 中以非前缀后缀的形式出现过。如果找不到输出 Just a legend。
输入一行一个字符串 s,输出一行一个字符串 p。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 
 | const int MAXN = 1e6 + 10;int n,m;
 int z[MAXN];
 char s[MAXN];
 
 
 void exkmp() {
 n = strlen(s + 1);
 int L = 1, R = 0;
 z[1] = 0;
 for(int i = 2; i <= n; i++) {
 if(i > R)  z[i] = 0;
 else  {
 int k = i - L + 1;
 z[i] = std::min(z[k],R - i + 1);
 }
 while(i + z[i] <= n + m + 1 && s[z[i] + 1] == s[i + z[i]]) ++z[i];
 if(i + z[i] - 1 > R)
 L = i,R = i + z[i] - 1;
 }
 int ans = 0,x = 0;
 for(int i=1;i<=n;i++) {
 if(i + z[i] - 1 == n) {
 if(x >= z[i]) {
 ans = std::max(ans,z[i]);
 }
 }
 x = std::max(x,z[i]);
 }
 if(!ans) {
 std::cout << "Just a legend" << endl;
 } else {
 for(int i=1;i<=ans;i++) std::cout << s[i];
 }
 }
 
 void JiuCherish(){
 std::cin >> s + 1;
 exkmp();
 }
 
 |