1 条题解
-
0
自动搬运
来自洛谷,原作者为

Alex_Wei
**搬运于
2025-08-24 22:49:23,当前版本为作者最后更新于2023-08-21 18:09:56,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
*P9543 [湖北省选模拟 2023] 日记 / diary
相当酷的一道题目。
设 ,。
的数据范围告诉我们题目的时间复杂度只能是线性或常数极小的单 。
首先让我们思考线性时间能求出哪些信息。KMP,扩展 KMP,Manacher,Lyndon 分解,最小表示法等字符串算法都是可以接受的,不过至于 SA,SAM 这样的后缀数据结构就别想了。因为本题和回文与字典序没有太大关系,而和 “一个字符串在另一个字符串中出现” 密切相关,所以我们猜测本题的主要算法是 KMP 和扩展 KMP。
不妨将限制放宽一些。如果不要求得到的字符串包含 作为子串,能做吗?必须能。构造 且 ,可知原问题不弱于:任选 的可空前缀 与可空后缀 ,求本质不同的 的数量。
该问题容易在线性时间内解决:对于每个本质不同的 ,只在最后一个分割点处统计它,即枚举 与 的最长公共前缀长度 ,并认为 。因此,对于每个 ,所有合法的 形如:若 ,则 为任意后缀;若 即 ,则 或 ,否则 与 的最长公共前缀还可以更长,即 可以更大。用总数 减去对每个 ,第一个字符等于 的非空后缀数量,即字符 在 中出现的次数。
尝试加入 在 中出现的限制,有三种情况: 在 中出现; 在 中出现; 等于 的非空后缀加上 的非空前缀。但这三种情况可能同时发生,再加上本质不同的要求,根本无从下手。但很显然, 在 或 中出现的情况更容易考虑一些,因为它只与 或 相关,而不同时与 相关。我们希望尽可能排除这两种情况,简化问题。
先看看用上面的算法能推出哪些东西吧:设 表示不要求 在 中出现过, 且 的本质不同的 的数量,可以线性计算。容斥是必要的。首先用 估计答案,但是多算了不包含 的 。只要 或 包含 ,那么 一定包含 。故考虑设 在 中第一次出现的 结束位置 为 ,最后一次出现的 开始位置 为 ,若 的结束位置在 及其右侧,或 的开始位置在 及其左侧,那么 一定包含 。特别地,若 没有在 中出现,可认为 ,。
在容斥减去不包含 的 的数量时,可以限制 且 。用 估计这个值,将导致答案少算了 存在 分割方式使得 且 的包含 的 的数量。
将 减去 , 加上 ,我们将问题转化为了:计算 的结束位置在 及其左侧, 的结束位置在 及其右侧,且 在 中出现过的本质不同的 的数量。根据 和 的定义,可知 和 不可能单独包含 。因此, 必然等于 的某个 非空 后缀加上 的某个 非空 前缀。在这个前提下,关于 “ 在 中出现” 的限制,有两种思路:
思路一:枚举 ,那么合法的 满足:存在 使得 的长度为 的后缀等于 的长度为 的前缀,且 的长度为 的前缀等于 的长度为 的后缀。
- 刻画 的形态:建出 的失配树,设 与 的最长公共后缀前缀为 (KMP 求出),那么 是失配树上 的祖先。
- 刻画 的形态:建出 的反串的失配树,设 与 的最长公共前缀后缀为 ,那么 是失配树上 的祖先。固定 ,合法的 是 的反串的失配树的某棵子树。因此合法的 是该失配树的若干子树的并,这很难处理,更何况还有本质不同的限制。这种思路并不可行。
思路二:从 中删去 ,设 和 变成了 ( 是 的前缀,且 )和 ( 是 的后缀,且 ),枚举 ,设 与 的最长公共前缀为 ,那么要求 与 的最长公共后缀 。
先不管本质不同,合法的 二元组数量是可以计算的:对每个 ,使用扩展 KMP 求出 与 的最长公共前缀 ;对每个 ,使用扩展 KMP 求出 与 的最长公共后缀 。注意到 和 均小于 ,所以 不会以位置 结尾(否则要求 完全包含 ),同理 不会以位置 开头。因此,合法的 二元组数量就等于 $\sum_{i = 1} ^ c\sum_{j = d} ^ n [zs_i + zs'_j \geq m]$。这是一维偏序,复杂度线性。
加入本质不同的限制,我们希望在最小的 的位置统计 。不能存在 合法 的 使得 且 ,为此,尝试探究什么样的 合法 二元组需 要舍去。
考虑两个 合法 二元组 和 满足 。不妨设 ,则 需要被舍去。设 ,则 (否则 在 中出现)。
- 根据 ,可知 与 有长度为 的公共后缀前缀,且 与 有长度为 的公共前缀后缀。又因为 的起始位置在 及其右侧,所以后者等价于 与 有长度为 的公共后缀,即 。
- 设 ,那么 且 为 的 border。这说明 有长度为 的 period,继而推出 有长度为 的 period。
综上,得 需要被舍去的必要条件:存在 同时满足
- 是 的 period。
- 与 有长度为 的公共后缀前缀。
- 。
充分吗?充分性是容易证明的。
枚举 。显然我们只关心最小的 使得 是 的 period 且 与 有长度为 的公共后缀前缀,因为对于所有这样的 ,如果 小于最小的 ,那么更大的 也不会让它被舍去。
综上,枚举 ,求出对应的最小的 (标记所有 period,然后在失配树上 DP),合法的 的起始位置 满足:
- 。
- 。
看上去是二维偏序,但这就是 ,还是一维偏序,前缀和即可。
时间复杂度 。
#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 */
- 1
信息
- ID
- 9085
- 时间
- 2000ms
- 内存
- 1024MiB
- 难度
- 7
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者