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

Alex_Wei
**搬运于
2025-08-24 22:07:18,当前版本为作者最后更新于2022-11-05 08:09:38,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
P5115 Check, Check, Check one two!
看到题目,我首先想到建出正反串 SA 及其 的笛卡尔树,并在一棵树上启发式合并,另一棵树上用 P4211 LCA 套路做,掐指一算发现时间复杂度是 ,虽然离线(枚举 LCA,考虑较小子树对较大子树的贡献,将询问离线扫描线)后三个 分别是启发式合并,BIT 和树剖,显然卡不满,但是依然非常难写。
稍微观察一下 和 ,它们拼接起来形成长为 的相等子串,联想到优秀的拆分,这启发我们在 $(i - \mathrm{lcs}(i, j) + 1, j - \mathrm{lcs}(i, j) + 1)$ 处统计贡献。因为相等子串的要求 极长,否则 或 可以更大,所以枚举 ,若 ,则 产生贡献。进一步地,我们发现贡献和 具体无关,仅和 相关,为 $f(L) = \sum\limits_{p = 1} ^ L p(L - p + 1) [p\leq k_1][L - p + 1 \leq k_2]$。 可以 预处理。
对于 的要求,直接容斥。问题转化为求与任意两个 的 相关的式子。经典套路,直接对 做扫描单调栈,对于当前 实时维护 $\sum\limits_{1\leq j < i} f(\min_{p = j + 1} ^ i ht_p)$ 即可。时间复杂度 。
理论可以做到关于长度加字符集线性(线性 SA,线性区间 RMQ),但不实用。
听说官方题解是 的边分树,应该是对题解一开始的 思路应用更多套路。对比两种做法,直接硬做没有用到拼接成相等子串的性质,而扫描单调栈巧妙运用了该性质。对于前者,可以扩展至无法拼接的问题而后者不能,如给定排列 ,将原问题 换成 。对于后者,可以扩展至任意容易快速计算 的情形,如 。
#include <bits/stdc++.h> using namespace std; using ull = unsigned long long; constexpr int N = 1e5 + 5; ull f[N], ans; int n, k1, k2; char s[N]; ull S1(ull x) {return x * (x + 1) / 2;} ull S2(ull x) {return x * (x + 1) * (x + x + 1) / 6;} int sa[N], rk[N], ork[N], buc[N], id[N], ht[N]; bool cmp(int a, int b, int w) {return ork[a] == ork[b] && ork[a + w] == ork[b + w];} void build() { int m = 1 << 7, p = 0; for(int i = 1; i <= n; i++) buc[rk[i] = s[i]]++; for(int i = 1; i <= m; i++) buc[i] += buc[i - 1]; for(int i = n; i; i--) sa[buc[rk[i]]--] = i; for(int w = 1; ; w <<= 1, m = p, p = 0) { for(int i = n - w + 1; i <= n; i++) id[++p] = i; for(int i = 1; i <= n; i++) if(sa[i] > w) id[++p] = sa[i] - w; memset(buc, 0, sizeof(buc)); memcpy(ork, rk, sizeof(rk)), p = 0; for(int i = 1; i <= n; i++) buc[rk[i]]++; for(int i = 1; i <= m; i++) buc[i] += buc[i - 1]; for(int i = n; i; i--) sa[buc[rk[id[i]]]--] = id[i]; for(int i = 1; i <= n; i++) rk[sa[i]] = cmp(sa[i - 1], sa[i], w) ? p : ++p; if(p == n) break; } for(int i = 1, k = 0; i <= n; i++) { if(k) k--; while(s[i + k] == s[sa[rk[i] - 1] + k]) k++; ht[rk[i]] = k; } } ull calc(char lim) { static int stc[N], w[N], top; ull cur = 0, ans = top = 0; for(int i = 2; i <= n; i++) { int wid = lim ? s[sa[i - 1] - 1] == lim : 1; while(top && stc[top] >= ht[i]) cur -= w[top] * f[stc[top]], wid += w[top--]; stc[++top] = ht[i], w[top] = wid, cur += wid * f[ht[i]]; if(lim ? s[sa[i] - 1] == lim : 1) ans += cur; } return ans; } int main() { #ifdef ALEX_WEI FILE* IN = freopen("1.in", "r", stdin); FILE* OUT = freopen("1.out", "w", stdout); #endif cin >> s + 1 >> k1 >> k2, n = strlen(s + 1); for(int L = 1; L <= n; L++) { int l = max(1, L - k2 + 1), r = min(L, k1); if(l > r) break; f[L] = (S1(r) - S1(l - 1)) * (L + 1) - (S2(r) - S2(l - 1)); } build(), ans += calc(0); for(int i = 0; i < 26; i++) ans -= calc('a' + i); cout << ans << "\n"; return 0; }
- 1
信息
- ID
- 4099
- 时间
- 2000ms
- 内存
- 500MiB
- 难度
- 7
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者