P9543 [湖北省选模拟 2023] 日记 / diary

· · 题解

*P9543 [湖北省选模拟 2023] 日记 / diary

相当酷的一道题目。

n = |S|m = |T|

首先让我们思考线性时间能求出哪些信息。KMP,扩展 KMP,Manacher,Lyndon 分解,最小表示法等字符串算法都是可以接受的,不过至于 SA,SAM 这样的后缀数据结构就别想了。因为本题和回文与字典序没有太大关系,而和 “一个字符串在另一个字符串中出现” 密切相关,所以我们猜测本题的主要算法是 KMP 和扩展 KMP。 不妨将限制放宽一些。如果不要求得到的字符串包含 $T$ 作为子串,能做吗?**必须能**。构造 $|T| = 1$ 且 $S_1 = S_n = T_1$,可知原问题不弱于:任选 $S$ 的可空前缀 $P$ 与可空后缀 $Q$,求本质不同的 $P + Q$ 的数量。 该问题容易在线性时间内解决:对于每个本质不同的 $R = P + Q$,只在最后一个分割点处统计它,即枚举 $R$ 与 $S$ 的最长公共前缀长度 $L$,并认为 $P = S_{1\sim L}$。因此,对于每个 $P$,所有合法的 $Q$ 形如:若 $P = S$,则 $Q$ 为任意后缀;若 $P\neq S$ 即 $L < n$,则 $|Q| = 0$ 或 $Q_1 \neq S_{L + 1}$,否则 $S$ 与 $R$ 的最长公共前缀还可以更长,即 $L$ 可以更大。用总数 $(n + 1) ^ 2$ 减去对每个 $0 \leq L < N$,第一个字符等于 $S_{L + 1}$ 的非空后缀数量,即字符 $S_{L + 1}$ 在 $S$ 中出现的次数。 尝试加入 $T$ 在 $R$ 中出现的限制,有三种情况:$T$ 在 $P$ 中出现;$T$ 在 $Q$ 中出现;$T$ 等于 $P$ 的非空后缀加上 $Q$ 的非空前缀。但这三种情况可能同时发生,再加上本质不同的要求,根本无从下手。但很显然,$T$ 在 $P$ 或 $Q$ 中出现的情况更容易考虑一些,因为它只与 $P$ 或 $Q$ 相关,而不同时与 $P, Q$ 相关。我们希望尽可能排除这两种情况,简化问题。 先看看用上面的算法能推出哪些东西吧:设 $f(i, j)$ 表示不要求 $T$ 在 $R$ 中出现过,$|P| \leq i$ 且 $|Q|\leq n - i + 1$ 的本质不同的 $R$ 的数量,可以线性计算。容斥是必要的。首先用 $f(n, 1)$ 估计答案,但是多算了不包含 $T$ 的 $R$。只要 $P$ 或 $Q$ 包含 $T$,那么 $R$ 一定包含 $T$。故考虑设 $T$ 在 $S$ 中第一次出现的 **结束位置** 为 $c$,最后一次出现的 **开始位置** 为 $d$,若 $P$ 的结束位置在 $c$ 及其右侧,或 $Q$ 的开始位置在 $d$ 及其左侧,那么 $R$ 一定包含 $T$。特别地,若 $T$ 没有在 $S$ 中出现,可认为 $c = n + 1$,$d = 0$。 在容斥减去不包含 $T$ 的 $R$ 的数量时,可以限制 $|P| < c$ 且 $|Q| < n - d + 1$。用 $f(c - 1, d + 1)$ 估计这个值,将导致答案少算了 **存在** 分割方式使得 $|P| < c$ 且 $|Q| < n - d + 1$ 的包含 $T$ 的 $R$ 的数量。 将 $c$ 减去 $1$,$d$ 加上 $1$,我们将问题转化为了:计算 $P$ 的结束位置在 $c$ 及其左侧,$Q$ 的结束位置在 $d$ 及其右侧,且 $T$ 在 $R$ 中出现过的本质不同的 $R$ 的数量。根据 $c$ 和 $d$ 的定义,可知 $P$ 和 $Q$ 不可能单独包含 $T$。因此,$T$ 必然等于 $P$ 的某个 **非空** 后缀加上 $Q$ 的某个 **非空** 前缀。在这个前提下,关于 “$T$ 在 $R$ 中出现” 的限制,有两种思路: **思路一**:枚举 $|P|$,那么合法的 $Q$ 满足:存在 $L$ 使得 $P$ 的长度为 $L$ 的后缀等于 $T$ 的长度为 $L$ 的前缀,且 $Q$ 的长度为 $n - L$ 的前缀等于 $T$ 的长度为 $n - L$ 的后缀。 - 刻画 $L$ 的形态:建出 $T$ 的失配树,设 $P$ 与 $T$ 的最长公共后缀前缀为 $len$(KMP 求出),那么 $L$ 是失配树上 $len$ 的祖先。 - 刻画 $Q$ 的形态:建出 $T$ 的反串的失配树,设 $Q$ 与 $T$ 的最长公共前缀后缀为 $len$,那么 $n - L$ 是失配树上 $len$ 的祖先。固定 $L$,合法的 $Q$ 是 $T$ 的反串的失配树的某棵子树。因此合法的 $Q$ 是该失配树的若干子树的并,这很难处理,更何况还有本质不同的限制。这种思路并不可行。 **思路二**:从 $R$ 中删去 $T$,设 $P$ 和 $Q$ 变成了 $P'$($P'$ 是 $P$ 的前缀,且 $P' \neq P$)和 $Q'$($Q'$ 是 $Q$ 的后缀,且 $Q'\neq Q$),枚举 $L = |P'|$,设 $S_{L + 1\sim c}$ 与 $T$ 的最长公共前缀为 $x$,那么要求 $S_{d\sim n - |Q'|}$ 与 $T$ 的最长公共后缀 $y\geq m - x$。 先不管本质不同,合法的 $(P', Q')$ 二元组数量是可以计算的:对每个 $1\leq i\leq c$,使用扩展 KMP 求出 $S_{i\sim c}$ 与 $T$ 的最长公共前缀 $zs_i$;对每个 $d\leq j\leq n$,使用扩展 KMP 求出 $S_{d\sim j}$ 与 $T$ 的最长公共后缀 $zs'_j$。注意到 $zs$ 和 $zs'$ 均小于 $m$,所以 $P'$ 不会以位置 $c$ 结尾(否则要求 $Q$ 完全包含 $T$),同理 $Q'$ 不会以位置 $d$ 开头。因此,合法的 $(P', Q')$ 二元组数量就等于 $\sum_{i = 1} ^ c\sum_{j = d} ^ n [zs_i + zs'_j \geq m]$。这是一维偏序,复杂度线性。 加入本质不同的限制,我们希望在最小的 $|P'|$ 的位置统计 $R$。不能存在 **合法** 的 $(P'', Q'')$ 使得 $P'' + T + Q'' = P' + T + Q'$ 且 $|P''| < |P'|$,为此,尝试探究什么样的 **合法** 二元组需 $(P', Q')$ 要舍去。 考虑两个 **合法** 二元组 $(P_1', Q_1')$ 和 $(P_2', Q_2')$ 满足 $R = P_1' + T + Q_1' = P_2' + T + Q_2'$。不妨设 $|P_1'| < |P_2'|$,则 $(P_2', Q_2')$ 需要被舍去。设 $per = |P_2'| - |P_1'|$,则 $per < m$(否则 $T$ 在 $P_2'$ 中出现)。 - 根据 $P_1' + T + Q_1' = P_2' + T + Q_2'$,可知 $P_2'$ 与 $T$ 有长度为 $per$ 的公共后缀前缀,且 $Q_1'$ 与 $T$ 有长度为 $per$ 的公共前缀后缀。又因为 $Q_1'$ 的起始位置在 $d$ 及其右侧,所以后者等价于 $S_{d\sim n - |Q_2'|}$ 与 $T$ 有长度为 $per$ 的公共后缀,即 $zs'_{n - |Q_2'|} \geq per$。 - 设 $R = P_1' + T' + Q_2'$,那么 $|T'| = m + per$ 且 $T$ 为 $T'$ 的 border。这说明 $T'$ 有长度为 $per$ 的 period,继而推出 $T$ 有长度为 $per$ 的 period。 综上,得 $(P_2', Q_2')$ 需要被舍去的必要条件:存在 $per$ 同时满足 - $per$ 是 $T$ 的 period。 - $P_2'$ 与 $T$ 有长度为 $per$ 的公共后缀前缀。 - $zs'_{n - |Q_2'|} \geq per$。 充分吗?充分性是容易证明的。 枚举 $|P_2'|$。显然我们只关心最小的 $per$ 使得 $per$ 是 $T$ 的 period 且 $P_2'$ 与 $T$ 有长度为 $per$ 的公共后缀前缀,因为对于所有这样的 $per$,如果 $zs'_{n - |Q_2'|}$ 小于最小的 $per$,那么更大的 $per$ 也不会让它被舍去。 综上,枚举 $0\leq i = |P'| < c$,求出对应的最小的 $per$(标记所有 period,然后在失配树上 DP),合法的 $Q'$ 的起始位置 $d < j \leq n + 1$ 满足: - $zs_{i + 1} + zs'_{j - 1} \geq m$。 - $zs'_{j - 1} < per$。 看上去是二维偏序,但这就是 $m - zs_{i + 1} \leq zs'_{j - 1} < per$,还是一维偏序,前缀和即可。 时间复杂度 $\mathcal{O}(n + m)$。 ```cpp #include <bits/stdc++.h> using namespace std; using ll = long long; bool Mbe; constexpr int N = 1e7 + 5; ll ans; int n, m, f[N], fst = -1, lst = -1; int zs[N], _zs[N], zt[N]; int ns[N], nt[N], buc[N]; char s[N], t[N]; void calcnxt(int *nt, int *ns) { for(int i = 2; i <= m; i++) { int j = nt[i - 1]; while(j && t[j + 1] != t[i]) j = nt[j]; nt[i] = j + (t[j + 1] == t[i]); } for(int i = 1; i <= n; i++) { int j = ns[i - 1]; while(j && t[j + 1] != s[i]) j = nt[j]; ns[i] = j + (t[j + 1] == s[i]); } } void calcz(int *zt, int *zs) { int l = 1, r = 0; for(int i = 2; i <= m; i++) { int j = i > r ? 0 : min(zt[i - l + 1], r - i + 1); while(t[j + 1] == t[i + j]) j++; if(i + j > r) l = i, r = i + j - 1; zt[i] = j; } l = 1, r = 0; for(int i = 1; i <= n; i++) { int j = i > r ? 0 : min(zt[i - l + 1], r - i + 1); while(i + j <= n && t[j + 1] == s[i + j]) j++; if(i + j > r) l = i, r = i + j - 1; zs[i] = j; } } ll calc(int l, int r) { ll res = 1ll * (l + 1) * (n - r + 2); vector<int> cnt(26); for(int i = r; i <= n; i++) cnt[s[i] - 'a']++; for(int i = 1; i <= l; i++) res -= cnt[s[i] - 'a']; return res; } bool Med; int main() { fprintf(stderr, "%.3lf MB\n", (&Mbe - &Med) / 1048576.0); #ifdef ALEX_WEI FILE *IN = freopen("diary.in", "r", stdin); FILE *OUT = freopen("diary.out", "w", stdout); #endif ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); cin >> s + 1 >> t + 1; n = strlen(s + 1), m = strlen(t + 1); calcz(zt, _zs); reverse(s + 1, s + n + 1); reverse(_zs + 1, _zs + n + 1); reverse(t + 1, t + m + 1); calcz(zt, zs), calcnxt(nt, ns); for(int i = 0; i <= m; i++) f[i] = m; for(int i = nt[m]; i; i = nt[i]) f[m - i] = m - i; for(int i = 1; i <= m; i++) f[i] = min(f[i], f[nt[i]]); for(int i = m; i <= n; i++) if(ns[i] == m) fst == -1 && (fst = i - 1), lst = i - m + 2; if(fst != -1) ans = calc(n, 1) - calc(fst, lst); else fst = n, lst = 1; for(int i = 1; i <= fst; i++) zs[i] = min(zs[i], fst - i + 1); for(int i = lst; i <= n; i++) buc[_zs[i] = min(_zs[i], i - lst + 1)]++; for(int i = 1; i <= n; i++) buc[i] += buc[i - 1]; for(int i = 1; i <= fst; i++) { int l = m - zs[i], r = min(n, f[ns[i - 1]] - 1); if(l <= r) ans += buc[r] - buc[l - 1]; } cout << ans << "\n"; cerr << 1e3 * clock() / CLOCKS_PER_SEC << " ms\n"; return 0; } /* g++ diary.cpp -o diary -O2 -std=c++14 -DALEX_WEI */ ```