目录php
众所周知,\(\mathrm{KMP}\) 算法是最为经典的单模板字符串匹配问题的线性解法。那么 \(\mathrm{ExKMP}\) 字面意义是 \(\mathrm{KMP}\) 的扩展,那么它是解决什么问题呢?c++
存在母串 \(S\) 和子串 \(T\) ,设 \(|S| = n, |T| = m\) ,求 \(T\) 与 \(S\) 的每个后缀的最长公共前缀 \((\mathrm{LCP})\)。git
设 \(extend\) 数组, \(extend[i]\) 表示 \(T\) 与 \(S_{i \sim n}\) 的 \(\mathrm{LCP}\) ,对于 \(i \in [1, n]\) 求 \(extend[i]\) 。算法
\(1 \le m \le n \le 10^6\)数组
如下的字符串下标均从 \(1\) 开始标号。spa
本文参考了这位 大佬的讲解 。.net
其实能够直接用 \(SA / SAM\) 解决,可是太大材小用了。。。(但彷佛不太好作到 \(O(n)\) 有一种是作到 \(O(n) - O(1) \mathrm{RMQ}\) )debug
对于通常的 \(\mathrm{KMP}\) 只须要求全部 \(extend[i] = m\) 的位置,那么 \(\mathrm{ExKMP}\) 就是须要求出这个 \(extend[i]\) 数组。code
举个例子更好理解。blog
令 \(S = \underline{aaaabaa}, T = \underline{aaaaa}\) 。
S: a a a a b a a | | | | X T: a a a a a咱们知道 \(extend[1] = 4\) ,而后计算 \(extend[2]\) ,咱们发现从新匹配是很浪费时间的。
因为 \(S_{1 \sim 4} = T_{1\sim 4}\) ,那么 \(S_{2 \sim 4} = T_{2 \sim 4}\) 。
此时咱们须要一个辅助的匹配数组 \(next[i]\) 表示 \(T_{i \sim m}\) 与 \(T\) 的 \(\mathrm{LCP}\) 。
咱们知道 \(next[2] = 4\) ,那么 \(T_{2 \sim 5} = T_{1 \sim 4} \Rightarrow T_{2 \sim 4} = T_{1 \sim 3}\) 。
因此能够直接从 \(T_4\) 开始和 \(S_5\) 匹配,此时发现会失配,那么 \(extend[2] = 3\) 。
这其实就是 \(\mathrm{ExKMP}\) 的主要思想,下面简述其匹配的过程。
此处假设咱们已经获得了 \(next[i]\) 。
当前咱们从前日后依次递推 \(extend[i]\) ,假设当前递推完前 \(k\) 位,要求 \(k + 1\) 位。
此时 \(extend[1 \sim k]\) 已经算完,假设以前 \(T\) 能匹配 \(S\) 的后缀最远的位置为 \(p = \max_{i < k} (i + extend[i] - 1)\) ,对应取到最大值的位置 \(i\) 为 \(pos\) 。
S: 1 ... pos ... k k+1 ... p ...
那么根据 \(extend\) 数组定义有 \(S_{pos \sim p} = T_{1 \sim p - pos + 1} \Rightarrow S_{k + 1 \sim p} = T_{k - pos + 2 \sim p -pos + 1}\) 。
令 \(len = next[k - pos + 2]\) ,分如下两种状况讨论。
\(k + len < p\) 。
S: 1 ... pos ... k k+1 ... k+len k+len+1 ... p ... | | | X T: 1 ... len len+1 ...
此时咱们发现 \(S_{k + 1 \sim k + len} = T_{1 \sim len}\) 。
因为 \(next[k - pos + 2] = len\) 因此 \(T_{k + len + pos + 2} \not = T_{len + 1}\) 。
又因为 \(S_{k + len + 1} = T_{k + len - pos + 2}\) 因此 \(S_{k + len + 1} \not = T_{len + 1}\) 。
这意味着 \(extend[k + 1] = len\) 。
\(k + len \ge p\)
S: 1 ... pos ... k k+1 ... p p+1 ... ... | | | ? T: 1 ... ... p-k+2 ... len ...
那么 \(S_{p + 1}\) 以后的串咱们都从何尝试匹配过,不知道其信息,咱们直接暴力向后依次匹配便可,直到失配停下来。
若是 \(extend[k + 1] + k > p\) 要更新 \(p\) 和 \(pos\) 。
前面咱们假设已经求出 \(next\) ,但如何求呢?
其实和 \(\mathrm{KMP}\) 是很相似的,咱们至关于 \(T\) 本身匹配本身每一个后缀的答案,此处须要的 \(next\) 全都在前面会计算过。
和前面匹配的过程是如出一辙的。
下面来分析一下算法的时间复杂度。
对于第一种状况,无需作任何匹配便可计算出 \(extend[i]\) 。
对于第二种状况,都是从未被匹配的位置开始匹配,匹配过的位置再也不匹配,也就是说对于母串的每个位置,都只匹配了一次,因此算法整体时间复杂度是 \(O(n)\) 的。
注意 \(k + 1 = i\) ,不要弄错下标了。
#include <bits/stdc++.h> #define For(i, l, r) for (register int i = (l), i##end = (int)(r); i <= i##end; ++i) #define Fordown(i, r, l) for (register int i = (r), i##end = (int)(l); i >= i##end; --i) #define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i) #define Set(a, v) memset(a, v, sizeof(a)) #define Cpy(a, b) memcpy(a, b, sizeof(a)) #define debug(x) cout << #x << ": " << (x) << endl #define next Next using namespace std; template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; } template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; } inline int read() { int x(0), sgn(1); char ch(getchar()); for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1; for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48); return x * sgn; } void File() { #ifdef zjp_shadow freopen ("1461.in", "r", stdin); freopen ("1461.out", "w", stdout); #endif } const int N = 1e6 + 1e3; void Get_Next(char *S, int *next) { int lenS = strlen(S + 1), p = 1, pos; next[1] = lenS; // 对于 next[1] 要特殊考虑 while (p + 1 <= lenS && S[p] == S[p + 1]) ++ p; next[pos = 2] = p - 1; // next[2] 是为了初始化 For (i, 3, lenS) { // 注意此时 k + 1 = i int len = next[i - pos + 1]; if (len + i < p + 1) next[i] = len; // 对应上面第一种状况 else { int j = max(p - i + 1, 0); // 找到前面对于 子串 最靠后已经匹配的位置 while (i + j <= lenS && S[j + 1] == S[i + j]) ++ j; // 第二种须要暴力匹配 p = i + (next[pos = i] = j) - 1; // 记得更新 p, pos } } } void ExKMP(char *S, char *T, int *next, int *extend) { int lenS = strlen(S + 1), lenT = strlen(T + 1), p = 1, pos; while (p <= lenT && S[p] == T[p]) ++ p; p = extend[pos = 1] = p - 1; // 初始化 extend[1] For (i, 2, lenS) { int len = next[i - pos + 1]; if (len + i < p + 1) extend[i] = len; else { int j = max(p - i + 1, 0); while (i + j <= lenS && j <= lenT && T[j + 1] == S[i + j]) ++ j; p = i + (extend[pos = i] = j) - 1; } } // 和上面基本如出一辙啦 } char S[N], T[N]; int next[N], extend[N]; int main () { File(); scanf ("%s", S + 1); scanf ("%s", T + 1); Get_Next(T, next); ExKMP(S, T, next, extend); For (i, 1, strlen(S + 1)) printf ("%d%c", extend[i], i == iend ? '\n' : ' '); return 0; }
给你一个字符串 \(S\) ,定义 \(num\) 数组 --- 对于字符串 \(S\) 的前 \(i\) 个字符构成的子串,既是它的后缀同时又是它的前缀,而且 该后缀与该前缀不重叠 ,将这种字符串的数量记做 \(num[i]\) 。
求 \(\prod_{i = 1}^{|S|} (num[i] + 1) \pmod {10^{9}+7}\)
若是会 \(\mathrm{ExKMP}\) 就是裸题了。
而后考虑对于每一个 \(S\) 的后缀 \(i\) 会被算多少遍,其实就是对于以 \([i, \min(2 \times (i - 1), i + next[i] - 1)]\) 为结尾的全部前缀有贡献,那么直接差分便可。
复杂度是 \(O(\sum |S|)\) 的。
前面的板子就再也不放了。
const int N = 1e6 + 1e3, Mod = 1e9 + 7; char str[N]; int num[N], next[N]; int main () { File(); for (int cases = read(); cases; -- cases) { scanf ("%s", str + 1); Set(num, 0); Get_Next(str, next); int n = strlen(str + 1); For (i, 2, n) if (next[i]) ++ num[i], -- num[min(i * 2 - 1, i + next[i])]; int ans = 1; For (i, 1, n) ans = 1ll * ans * ((num[i] += num[i - 1]) + 1) % Mod; printf ("%d\n", ans); } return 0; }
给你一个由数字构成的字符串 \(a\) ,问你有多少种划分方式,使得每段不含前导 \(0\) ,而且每段的数字大小在 \([l, r]\) 之间。答案对于 \(998244353\) 取模。
\(1 \le a \le 10^{1000000}, 0 \le l \le r \le 10^{1000000}\)
考虑暴力 \(dp\) ,令 \(dp_i\) 为以 \(i\) 为一段结束的方案数。对于填表法是没有那么好转移的,(由于前导 \(0\) 的限制是挂在前面那个点上)咱们考虑刷表法。
那么转移为
\[ dp_j = dp_j + dp_i~~\{j~|~a_i \not = 0 \& l \le a_{i \sim j} \le r\} \]
咱们发现 \(dp_i\) 能转移到的 \(j\) 必定是一段连续的区间。
咱们就须要快速获得这段区间,首先不难发现 \(j\) 对应的位数区间是能够很快肯定的,就是 \([l + |L| - 1, i + |R| - 1]\) 。
可是若是位数同样的话须要多花费 \(O(n)\) 的时间去逐位比较大小。
有什么快速的方法吗?不难想到比较两个数字大小的时候是和字符串同样的,就是 \(\mathrm{LCP}\) 的后面一位。
那么咱们用 \(\mathrm{ExKMP}\) 快速预处理 \(extend(\mathrm{LCP})\) 就能够了。
const int N = 1e6 + 1e3, Mod = 998244353; inline void Add(int &a, int b) { if ((a += b) >= Mod) a -= Mod; } char S[N], L[N], R[N]; template<typename T> inline int dcmp(T lhs, T rhs) { return (lhs > rhs) - (lhs < rhs); } inline int Cmp(int l, int r, char *cmp, int *Lcp, int len) { if (r - l + 1 != len) return dcmp(r - l + 1, len); return l + Lcp[l] > r ? 0 : dcmp(S[l + Lcp[l]], cmp[Lcp[l] + 1]); } int lenL, lenR, tmp[N], EL[N], ER[N]; inline bool Check(int x, int y) { return Cmp(x, y, L, EL, lenL) >= 0 && Cmp(x, y, R, ER, lenR) <= 0; } int tag[N], dp = 1; int main () { File(); scanf ("%s", S + 1); int n = strlen(S + 1); scanf ("%s", L + 1); lenL = strlen(L + 1); Get_Next(L, tmp); ExKMP(S, L, tmp, EL); scanf ("%s", R + 1); lenR = strlen(R + 1); Get_Next(R, tmp); ExKMP(S, R, tmp, ER); tag[1] = Mod - 1; For (i, 1, n) { int l, r; if (S[i] == '0') { if (L[1] == '0') l = r = i; else { Add(dp, tag[i]); continue; } } else { l = i + lenL - 1; if (!Check(i, l)) ++ l; r = i + lenR - 1; if (!Check(i, r)) -- r; } if (l <= r) Add(tag[l], dp), Add(tag[r + 1], Mod - dp); Add(dp, tag[i]); } printf ("%d\n", (dp + Mod) % Mod); return 0; }