KMP 模板1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 char s[maxn], p[maxn];int nxt[maxn];void getnxt (char *p) { int len = strlen (p), k = -1 , i = 0 ; nxt[0 ] = -1 ; while (i < len){ if (k == -1 || p[k] == p[i]) i++, k++, nxt[i] = k; else k = nxt[k]; } } int kmp (char *s, char *p) { getnxt (p); int slen = strlen (s), plen = strlen (p), i = 0 , j = 0 ; while (i < slen && j < plen){ if (j == -1 || s[i] == p[j]) i++, j++; else j = nxt[j]; } if (j == plen) return i - j; return -1 ; }
模板2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 void getfail (int len, char * s, int fail[]) { fail[1 ] = 0 ; for (int i = 2 ; i <= len; i++) { int cur = fail[i - 1 ]; while (cur > 0 && s[cur + 1 ] != s[i]) cur = fail[cur]; if (s[cur + 1 ] == s[i]) ++cur; fail[i] = cur; } } void kmp (char *s, char *p) { int slen = strlen (s + 1 ), plen = strlen (p + 1 ), cur = 0 ; getfail (plen, p, nxt); for (int i = 1 ; i <= slen; i++) { while (cur > 0 && s[i] != p[cur + 1 ]) cur = nxt[cur]; if (p[cur + 1 ] == s[i]) cur++; if (cur == plen) { printf ("%d\n" , i - cur + 1 ); cur = nxt[cur]; } } }
扩展KMP 1 2 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 char s[maxn], t[maxn];int nxt[maxn], extend[maxn];void getnxt (char *s) { int n = strlen (s), p = 0 , k = 1 ; nxt[0 ] = n; while (p + 1 < n && s[p] == s[p + 1 ]) p++; nxt[1 ] = p; for (int i = 2 ; i < n; i++){ p = k + nxt[k] - 1 ; if (i + nxt[i - k] <= p) nxt[i] = nxt[i - k]; else { int j = max (p - i + 1 , 0 ); while (i + j < n && s[i + j] == s[j]) j++; nxt[i] = j; k = i; } } } void exkmp (char *t, char *s) { getnxt (s); int tlen = strlen (t), slen = strlen (s), p = 0 , k = 0 ; while (p < tlen && p < slen && t[p] == s[p]) p++; extend[0 ] = p; for (int i = 1 ; i < tlen; i++){ p = k + extend[k] - 1 ; if (i + nxt[i - k] <= p) extend[i] = nxt[i - k]; else { int j = max (p - i + 1 , 0 ); while (i + j < tlen && j < slen && t[i + j] == s[j]) j++; extend[i] = j; k = i; } } }
注意 参数均为前一个是文本串,后一个为模板串。
应用 求 $p$ 串在 $t$ 串中的位置可重复的出现次数 传送门:子串查找
$kmp$ 时,$j=plen$ 时更新答案,并将 $j$ 转移到对应的 $nxt$ 值。
求 $s$ 串的最短循环节 传送门:Power Strings
$getnxt()$ 后,$nxt[len]$ 表示 $s$ 串前缀和后缀匹配的最大长度。
如果 $len-nxt[len]$ 整除 $len$,则这个串有循环节,循环节长度就是 $len-nxt[len]$。
求每个前缀的出现次数 每经过模板串上的一个位置,这个前缀和他的前缀函数等等出现次数都 $+1$。
匹配时更新一下每个位置的经过次数,最后倒序根据前缀函数更新次数。
代码 1 2 3 4 5 6 7 8 getfail (strlen (t + 1 ), t, fail);for (int i = 1 ; i <= n; i++) { while (cur && t[cur + 1 ] != s[i]) cur = fail[cur]; if (t[cur + 1 ] == s[i]) cur++; ans[cur]++; if (cur == m) cur = fail[cur]; } for (int i = m; i >= 1 ; i--) ans[fail[i]] += ans[i];