P4248 [AHOI2013] 差异 题解

Description

给定一个长度为 nn 的字符串 SS,令 TiT_i 表示它从第 ii 个字符开始的后缀。求 1ijnlen(Ti)+len(Tj)2×lcp(Ti,Tj)\displaystyle \sum_{1\leq ij\leq n}\text{len}(T_i)+\text{len}(T_j)-2\times\text{lcp}(T_i,T_j)

其中,len(a)\text{len}(a) 表示字符串 aa 的长度,lcp(a,b)\text{lcp}(a,b) 表示字符串 aa 和字符串 bb 的最长公共前缀。

link

Solution

考虑把 SS 的反串的 parent 树建出来。

TiT_i 的 endpos 等价类是 xxTjT_j 的 endpos 等价类是 yy

由于 parent 树上任意一个点的父亲一定是这个点的后缀,所以 TiT_iTjT_j 的公共前缀一定就在反串 parent 树上 xxyy 某个公共祖先所表示的等价类里,而最长的一定就在 xxyy 的 LCA 上。

由于 Ti,TjT_i,T_jlcp(Ti,Tj)\text{lcp}(T_i,T_j) 一定都是反串的前缀,所以他们的长度一定都是所在等价类最大的,那么 len(Ti)+len(Tj)2×lcp(Ti,Tj)\text{len}(T_i)+\text{len}(T_j)-2\times\text{lcp}(T_i,T_j) 就等于 lenx+leny2×lenlca\text{len}_x+\text{len}_y-2\times \text{len}_\text{lca},如果把 parent 树的边权看作相邻两点 len\text{len} 的差值,那么原式就是 x,yx,y 的树上最短路径的长度。

然后对于每条边算贡献即可,即设 sizex\text{size}_x 表示 xx 的子树里反串前缀所在的等价类的个数,答案就是 (sizex×(nsizex)×(lenxlenfa))\displaystyle\sum\left(\text{size}_x\times(n-\text{size}_x)\times(\text{len}_x-\text{len}_\text{fa})\right)

时间复杂度:O(n)O(n)

Code

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#include <cstdio>
#include <iostream>
#include <vector>

// #define int int64_t

const int kMaxN = 1e6 + 5;

using i64 = int64_t;

int n, tot = 1, lst = 1, nxt[kMaxN][26], len[kMaxN], fa[kMaxN], sz[kMaxN];
i64 ans;
std::vector<int> G[kMaxN];

void ins(int c) {
int cur = ++tot, p = lst;
lst = cur;
len[cur] = len[p] + 1, sz[cur] = 1;
for (; p && !nxt[p][c]; p = fa[p]) nxt[p][c] = cur;
if (!p) {
fa[cur] = 1;
} else {
int q = nxt[p][c];
if (len[q] == len[p] + 1) {
fa[cur] = q;
} else {
int nw = ++tot;
fa[nw] = fa[q], len[nw] = len[p] + 1;
for (int i = 0; i < 26; ++i)
nxt[nw][i] = nxt[q][i];
fa[q] = fa[cur] = nw;
for (; p && nxt[p][c] == q; p = fa[p]) nxt[p][c] = nw;
}
}
}

void dfs(int u) {
for (auto v : G[u]) {
dfs(v);
sz[u] += sz[v];
ans += (i64)sz[v] * (n - sz[v]) * (len[v] - len[u]);
}
}

void dickdreamer() {
std::string s;
std::cin >> s;
n = s.size();
for (int i = n - 1; ~i; --i)
ins(s[i] - 'a');
for (int i = 2; i <= tot; ++i)
G[fa[i]].emplace_back(i);
dfs(1);
std::cout << ans << '\n';
}

int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
// std::cin >> T;
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}

P4248 [AHOI2013] 差异 题解
https://sobaliuziao.github.io/2023/07/22/post/593b1da7.html
作者
Egg_laying_master
发布于
2023年7月22日
许可协议