简介
长链剖分是一种树链剖分,与重链剖分不同的是,每个点的关键儿子是它向下高度最大的儿子。
长链剖分有许多优美的性质。例如,某个点的 $k$ 级祖先所在链的边数一定不小于 $k$;任意两点之间只会跨过 $\sqrt n$ 条链。另外,长链剖分还可以在线性的时间内解决状态规模为子树高度的许多 dp。
k 级祖先
题目描述
给定一棵 $n$ 个点的树,$q$ 次询问点 $x$ 的 $k$ 级祖先。
强制在线,复杂度要求 $O(n \log n) - O(1)$。
思路分析
考虑利用简介中提到的一个性质:“某个点的 $k$ 级祖先所在链的长度一定不小于 $k$”。我们先用倍增预处理出每个点向上跳 $2^t$ 步会到达哪个点。考虑 $k$ 在二进制下的最高位 $b$,记 $x$ 的 $2^b$ 级祖先为 $y$,然后我们只要求 $y$ 的 $k - 2^b$ 级祖先。首先有 $y$ 所在链的长度不小于 $2^b$,然后发现 $k - 2^b < 2^b$,所以 $y$ 所在链的长度一定超过 $k - 2^b$。我们只需要处理出每个链顶向上跳不超过链长步会到达哪些点,存进 std::vector
中即可 $O(1)$ 查询 $y$ 的 $k - 2^b$ 级祖先。时间复杂度 $O(n \log n) - O(1)$。
代码实现
这题 RE on #6, 7, 8 实在调不出来了,代码仅供参考。
1 |
|
2 | using namespace std; |
3 | |
4 | const int maxn = 3e5, logn = 18; |
5 | int n, m, dep[maxn + 3], mx[maxn + 3], ch[maxn + 3], fa[maxn + 3][logn + 3]; |
6 | int top[maxn + 3], bot[maxn + 3], len[maxn + 3], ans, lg[maxn + 3]; |
7 | vector<int> G[maxn + 3], S[maxn + 3]; |
8 | |
9 | void dfs(int u, int pa = 0) { |
10 | for (int i = 1; (fa[u][i] = fa[fa[u][i - 1]][i - 1]); i++); |
11 | for (int i = 0, v; i < G[u].size(); i++) { |
12 | v = G[u][i]; |
13 | if (v == pa) continue; |
14 | fa[v][0] = u; |
15 | dep[v] = dep[u] + 1; |
16 | dfs(v, u); |
17 | if (mx[v] + 1 > mx[u]) { |
18 | mx[u] = mx[v] + 1, ch[u] = v; |
19 | } |
20 | } |
21 | } |
22 | |
23 | void solve(int u, int pa = 0, int t = 1) { |
24 | top[u] = t; |
25 | for (int i = 0, v; i < G[u].size(); i++) { |
26 | v = G[u][i]; |
27 | if (v == pa || v == ch[u]) continue; |
28 | solve(v, u, v); |
29 | } |
30 | if (ch[u]) { |
31 | len[top[u]]++; |
32 | solve(ch[u], u, t); |
33 | } else { |
34 | bot[top[u]] = u; |
35 | } |
36 | S[top[u]].push_back(u); |
37 | if (u == t) { |
38 | for (int i = 1, x = u; i <= len[u]; i++) { |
39 | S[u].push_back(x = fa[x][0]); |
40 | } |
41 | } |
42 | } |
43 | |
44 | int query(int x, int k) { |
45 | if (k == 0) { |
46 | return x; |
47 | } else if (k > dep[x]) { |
48 | return 0; |
49 | } |
50 | int t = lg[k]; |
51 | x = fa[x][t], k -= 1 << t; |
52 | return S[top[x]][dep[bot[top[x]]] - dep[x] + k]; |
53 | } |
54 | |
55 | int main() { |
56 | scanf("%d", &n); |
57 | for (int i = 2; i <= n; i++) { |
58 | lg[i] = lg[i >> 1] + 1; |
59 | } |
60 | for (int i = 1, u, v; i < n; i++) { |
61 | scanf("%d %d", &u, &v); |
62 | G[u].push_back(v), G[v].push_back(u); |
63 | } |
64 | dfs(1); |
65 | solve(1); |
66 | scanf("%d", &m); |
67 | for (int x, k; m --> 0; ) { |
68 | scanf("%d %d", &x, &k); |
69 | x ^= ans, k ^= ans; |
70 | printf("%d\n", ans = query(x, k)); |
71 | } |
72 | return 0; |
73 | } |
贪心
题目描述
给定点带权的点数为 $n$ 的树,要选出 $k$ 条包含 $1$ 的祖先后代链,使得它们并的点权和最大。
$n \le 2 \times 10^5, 1 \le a_i < 2^{31}$。
思路分析
考虑带权的长链剖分,剖出的链取前 $k$ 条加起来即可。时间复杂度 $O(n \log n)$。
代码实现
1 |
|
2 | using namespace std; |
3 | |
4 | typedef long long ll; |
5 | const int maxn = 2e5; |
6 | int n, k, a[maxn + 3], id[maxn + 3]; |
7 | ll mx[maxn + 3]; |
8 | vector<int> G[maxn + 3]; |
9 | |
10 | void dfs(int u, int pa = 0) { |
11 | ll x = 0; |
12 | for (int i = 0, v; i < G[u].size(); i++) { |
13 | v = G[u][i]; |
14 | if (v == pa) continue; |
15 | dfs(v, u); |
16 | if (mx[id[v]] + a[u] > x) { |
17 | x = mx[id[v]] + a[u], id[u] = id[v]; |
18 | } |
19 | } |
20 | if (id[u]) { |
21 | mx[id[u]] += a[u]; |
22 | } else { |
23 | mx[u] = a[u], id[u] = u; |
24 | } |
25 | } |
26 | |
27 | int main() { |
28 | scanf("%d %d", &n, &k); |
29 | for (int i = 1; i <= n; i++) { |
30 | scanf("%d", &a[i]); |
31 | } |
32 | for (int i = 1, u, v; i < n; i++) { |
33 | scanf("%d %d", &u, &v); |
34 | G[u].push_back(v), G[v].push_back(u); |
35 | } |
36 | dfs(1); |
37 | sort(mx + 1, mx + n + 1); |
38 | reverse(mx + 1, mx + n + 1); |
39 | ll ans = 0; |
40 | for (int i = 1; i <= k; i++) { |
41 | ans += mx[i]; |
42 | } |
43 | printf("%lld\n", ans); |
44 | return 0; |
45 | } |
模版题
题目描述
记 $f(u, i)$ 为 $u$ 往下走 $i$ 步到达的点数,给定 $n$ 个点的树,对于每个点求出序列 $[f(u, 0), f(u, 1), \cdots]$ 中的最大值的最小下标。
数据范围:$n \le 10^6$。
思路分析
考虑长链剖分求 $f$,对于一个点,先继承特殊儿子的答案,然后对于其他儿子暴力更新即可。时间复杂度 $O(n)$。
代码实现
用 std::vector
实现,代码更短哦!
1 |
|
2 | using namespace std; |
3 | |
4 | const int maxn = 1e6; |
5 | int n, mx[maxn + 3], ch[maxn + 3], id[maxn + 3], ans[maxn + 3], res[maxn + 3]; |
6 | vector<int> G[maxn + 3], f[maxn + 3]; |
7 | |
8 | void dfs(int u, int pa = 0) { |
9 | for (int i = 0, v; i < G[u].size(); i++) { |
10 | v = G[u][i]; |
11 | if (v == pa) continue; |
12 | dfs(v, u); |
13 | if (mx[v] + 1 > mx[u]) { |
14 | mx[u] = mx[v] + 1, ch[u] = v; |
15 | } |
16 | } |
17 | int x = id[u] = ch[u] ? id[ch[u]] : u; |
18 | ans[u] = ch[u] ? ans[ch[u]] + 1 : 0, res[u] = ch[u] ? res[ch[u]] : 1; |
19 | f[x].push_back(1); |
20 | if (res[u] == 1) { |
21 | ans[u] = 0; |
22 | } |
23 | for (int i = 0, v; i < G[u].size(); i++) { |
24 | v = G[u][i]; |
25 | if (v == pa || v == ch[u]) continue; |
26 | int y = id[v]; |
27 | for (int j = 0; j < f[y].size(); j++) { |
28 | int p = f[x].size() - f[y].size() + j - 1; |
29 | f[x][f[x].size() - f[y].size() + j - 1] += f[y][j]; |
30 | int id = f[x].size() - p - 1, val = f[x][p]; |
31 | if (val > res[u] || (val == res[u] && id < ans[u])) { |
32 | ans[u] = id, res[u] = val; |
33 | } |
34 | } |
35 | } |
36 | } |
37 | |
38 | int main() { |
39 | scanf("%d", &n); |
40 | for (int i = 1, u, v; i < n; i++) { |
41 | scanf("%d %d", &u, &v); |
42 | G[u].push_back(v), G[v].push_back(u); |
43 | } |
44 | dfs(1); |
45 | for (int i = 1; i <= n; i++) { |
46 | printf("%d\n", ans[i]); |
47 | } |
48 | return 0; |
49 | } |
重建计划
题目描述
给定边带权的 $n$ 个点的树,选出一条长度在 $[L, U]$ 内的链,使得边权的平均值最小。
数据范围:$n \le 10^5$。
思路分析
首先可以想到二分答案,问题变成链的最大权值和是否超过 $0$。考虑长链剖分,设 $f(u, i)$ 表示 $u$ 的子树内以 $u$ 为端点的权值最大的长度为 $i$ 的路径长度。我们考虑使用线段树维护 $f$,从特殊儿子继承时只需要区间加,从其他儿子先更新答案(线段树区间最大值),后更新 $f$(暴力)即可。时间复杂度 $O(n \log^2 n)$。
代码实现
注意这个题不能用 std::vector
,而是要使用内存分配的技巧。对于每一条链,我们改变 dfs 的顺序,使得每条链的 dfs 序连续。这样开一个全局的线段树,一条链上的一段就可以对应线段树的一个区间了。推荐简单题使用 std::vector
,难题使用内存分配的方法。(因为难题用 std::vector
会写自闭的,详见 HOT Hotels)
1 |
|
2 | using namespace std; |
3 | |
4 | typedef double db; |
5 | const int maxn = 1e5, maxm = 1 << 18; |
6 | const db inf = 1e18; |
7 | int n, L, U, mxd[maxn + 3], ch[maxn + 3], top[maxn + 3], bot[maxn + 3]; |
8 | int cnt, wei[maxn + 3], dfn[maxn + 3]; |
9 | db val[maxn + 3], ans, mx[maxm + 3], tag[maxm + 3]; |
10 | vector<int> G[maxn + 3], W[maxn + 3]; |
11 | |
12 | void add(int u, int v, int w) { |
13 | G[u].push_back(v), W[u].push_back(w); |
14 | } |
15 | |
16 | void dfs_1(int u, int pa = 0) { |
17 | for (int i = 0, v, w; i < G[u].size(); i++) { |
18 | v = G[u][i], w = W[u][i]; |
19 | if (v == pa) continue; |
20 | wei[v] = w; |
21 | dfs_1(v, u); |
22 | if (mxd[v] + 1 > mxd[u]) { |
23 | mxd[u] = mxd[v] + 1, ch[u] = v; |
24 | } |
25 | } |
26 | } |
27 | |
28 | void dfs_2(int u, int pa = 0, int t = 1) { |
29 | dfn[u] = ++cnt; |
30 | top[u] = t; |
31 | if (ch[u]) { |
32 | dfs_2(ch[u], u, t); |
33 | bot[u] = bot[ch[u]]; |
34 | } else { |
35 | bot[u] = u; |
36 | } |
37 | for (int i = 0, v; i < G[u].size(); i++) { |
38 | v = G[u][i]; |
39 | if (v == pa || v == ch[u]) continue; |
40 | dfs_2(v, u, v); |
41 | } |
42 | } |
43 | |
44 | void upd(db &x, db y) { |
45 | x < y ? x = y : 0; |
46 | } |
47 | |
48 |
|
49 |
|
50 |
|
51 | |
52 | void build(int x, int l, int r) { |
53 | mx[x] = 0, tag[x] = 0; |
54 | if (l == r) { |
55 | return; |
56 | } |
57 | build(ls, l, mid); |
58 | build(rs, mid + 1, r); |
59 | } |
60 | |
61 | void push_down(int x) { |
62 | mx[ls] += tag[x], tag[ls] += tag[x]; |
63 | mx[rs] += tag[x], tag[rs] += tag[x]; |
64 | tag[x] = 0; |
65 | } |
66 | |
67 | void maintain(int x) { |
68 | mx[x] = max(mx[ls], mx[rs]); |
69 | } |
70 | |
71 | void chk_max(int x, int l, int r, int y, db z) { |
72 | if (l == r) { |
73 | mx[x] = max(mx[x], z); |
74 | return; |
75 | } |
76 | push_down(x); |
77 | if (y <= mid) { |
78 | chk_max(ls, l, mid, y, z); |
79 | } else { |
80 | chk_max(rs, mid + 1, r, y, z); |
81 | } |
82 | maintain(x); |
83 | } |
84 | |
85 | void modify(int x, int l, int r, int lx, int rx, db y) { |
86 | if (l >= lx && r <= rx) { |
87 | mx[x] += y, tag[x] += y; |
88 | return; |
89 | } |
90 | push_down(x); |
91 | if (lx <= mid) { |
92 | modify(ls, l, mid, lx, rx, y); |
93 | } |
94 | if (rx > mid) { |
95 | modify(rs, mid + 1, r, lx, rx, y); |
96 | } |
97 | maintain(x); |
98 | } |
99 | |
100 | db query(int x, int l, int r, int lx, int rx) { |
101 | if (l >= lx && r <= rx) { |
102 | return mx[x]; |
103 | } |
104 | push_down(x); |
105 | db ret = -inf; |
106 | if (lx <= mid) { |
107 | upd(ret, query(ls, l, mid, lx, rx)); |
108 | } |
109 | if (rx > mid) { |
110 | upd(ret, query(rs, mid + 1, r, lx, rx)); |
111 | } |
112 | return ret; |
113 | } |
114 | |
115 | db get(int x) { |
116 | return query(1, 1, n, x, x); |
117 | } |
118 | |
119 |
|
120 |
|
121 |
|
122 | |
123 | void solve(int u, int pa = 0) { |
124 | int x = dfn[u]; |
125 | if (ch[u]) { |
126 | solve(ch[u], u); |
127 | modify(1, 1, n, x + 1, dfn[bot[u]], val[ch[u]]); |
128 | int lft = x + L, rht = min(dfn[bot[u]], x + U); |
129 | if (lft <= rht) { |
130 | upd(ans, query(1, 1, n, lft, rht)); |
131 | } |
132 | } |
133 | for (int i = 0, v; i < G[u].size(); i++) { |
134 | v = G[u][i]; |
135 | if (v == pa || v == ch[u]) continue; |
136 | solve(v, u); |
137 | int y = dfn[v]; |
138 | for (int j = 0; j <= dfn[bot[v]] - y; j++) { |
139 | int lft = max(x + L - 1 - j, x), rht = min(x + U - 1 - j, dfn[bot[u]]); |
140 | if (lft <= rht) { |
141 | upd(ans, query(1, 1, n, lft, rht) + get(y + j) + val[v]); |
142 | } |
143 | } |
144 | for (int j = 0; j <= dfn[bot[v]] - y; j++) { |
145 | chk_max(1, 1, n, dfn[u] + j + 1, get(y + j) + val[v]); |
146 | } |
147 | } |
148 | } |
149 | |
150 | bool check(db x) { |
151 | build(1, 1, n); |
152 | for (int i = 2; i <= n; i++) { |
153 | val[i] = wei[i] - x; |
154 | } |
155 | ans = -inf; |
156 | solve(1); |
157 | return ans >= 0; |
158 | } |
159 | |
160 | int main() { |
161 | scanf("%d %d %d", &n, &L, &U); |
162 | for (int i = 1, u, v, w; i < n; i++) { |
163 | scanf("%d %d %d", &u, &v, &w); |
164 | add(u, v, w), add(v, u, w); |
165 | } |
166 | dfs_1(1); |
167 | dfs_2(1); |
168 | db l = 0, r = 1e6, mid; |
169 | while (l + 1e-4 < r) { |
170 | mid = (l + r) / 2; |
171 | if (check(mid)) { |
172 | l = mid; |
173 | } else { |
174 | r = mid; |
175 | } |
176 | } |
177 | printf("%.3lf\n", l); |
178 | return 0; |
179 | } |
HOT Hotels
题目描述
给定 $n$ 个点的树,求有多少点的三元组满足它们两两之间距离相等。
数据范围:$n \le 10^5$。
思路分析
发现三元组肯定有一个中心到三个点距离相等。记 $f(u, i)$ 表示 $u$ 子树内和它距离为 $i$ 的点数,$g(u, i)$ 表示 $u$ 子树内有多少点对满足它们到 LCA 的距离都为 $t$ 且 LCA 到 $u$ 的距离为 $t - i$(也就是说在上面加上长度为 $i$ 的链后可以形成方案)。那么转移方程如下:
1 | for(int i = 0;i <= n;i ++) ans += g[x][i] * (i == 0 ? 0 : f[to][i-1]) + g[to][i+1] * f[x][i]; |
2 | for(int i = 0;i <= n;i ++) g[x][i] += f[x][i] * (i == 0 ? 0 : f[to][i-1]) + g[to][i+1]; |
3 | for(int i = 1;i <= n;i ++) f[x][i] += f[to][i-1]; |
(转自 Treaker 的博客)
注意 $f(u, 0)$ 的初值为 $1$,dfs 完 $u$ 时还要让 $\text{ans} \leftarrow \text{ans} + g(u, 0)$。
那么使用长链剖分优化即可。时间复杂度 $O(n)$。
代码实现
用 std::vector
写的,由于 $g$ 数组转移的特殊,下标的变化很玄学,细节比较多。使用分配内存的方法就可以减少细节量。
1 | // f[x][i] = p[x][f[x].size() - i - 1] |
2 | // g[x][i] = q[x][i - mx(x) + 1] |
3 | // p[x][i] = f[x][f[x].size() - i - 1] |
4 | // q[x][i] = g[x][i + mx(x) - 1] |
5 | |
6 |
|
7 | using namespace std; |
8 | |
9 | typedef long long ll; |
10 | const int maxn = 1e5; |
11 | int n, tot, ch[maxn + 3], mx[maxn + 3], id[maxn + 3]; |
12 | ll ans; |
13 | vector<int> G[maxn + 3]; |
14 | vector<ll> f[maxn + 3], g[maxn + 3]; |
15 | |
16 | void dfs(int u, int pa = 0) { |
17 | for (int i = 0, v; i < G[u].size(); i++) { |
18 | v = G[u][i]; |
19 | if (v == pa) continue; |
20 | dfs(v, u); |
21 | if (mx[v] + 1 > mx[u]) { |
22 | mx[u] = mx[v] + 1, ch[u] = v; |
23 | } |
24 | } |
25 | int x = id[u] = ch[u] ? id[ch[u]] : ++tot; |
26 | f[x].push_back(1); |
27 | if (ch[u]) { |
28 | for (int t = 0; t < 2; t++) { |
29 | g[x].push_back(0); |
30 | } |
31 | } |
32 | for (int i = 0, v; i < G[u].size(); i++) { |
33 | v = G[u][i]; |
34 | if (v == pa || v == ch[u]) continue; |
35 | int y = id[v]; |
36 | for (int j = 1; j < f[x].size(); j++) if (j + mx[v] < g[y].size()) { |
37 | ans += f[x][f[x].size() - j - 1] * g[y][j + mx[v]]; |
38 | } |
39 | for (int j = 0; j < f[y].size(); j++) { |
40 | ans += g[x][j + mx[u]] * f[y][f[y].size() - j - 1]; |
41 | } |
42 | if (ch[v]) for (int j = 0; j <= mx[v]; j++) { |
43 | g[x][j - 1 + mx[u] - 1] += g[y][j + mx[v] - 1]; |
44 | } |
45 | for (int j = 0; j < f[y].size(); j++) { |
46 | g[x][j + mx[u]] += f[x][f[x].size() - (j + 1) - 1] * f[y][f[y].size() - j - 1]; |
47 | } |
48 | for (int j = 0; j < f[y].size(); j++) { |
49 | f[x][j + f[x].size() - f[y].size() - 1] += f[y][j]; |
50 | } |
51 | } |
52 | if (ch[u]) { |
53 | ans += g[x][mx[u] - 1]; |
54 | if (mx[u] - 2 >= 0) { |
55 | g[x][mx[u] - 2] = 0; |
56 | } |
57 | } |
58 | } |
59 | |
60 | int main() { |
61 | scanf("%d", &n); |
62 | for (int i = 1, u, v; i < n; i++) { |
63 | scanf("%d %d", &u, &v); |
64 | G[u].push_back(v), G[v].push_back(u); |
65 | } |
66 | dfs(1); |
67 | printf("%lld\n", ans); |
68 | return 0; |
69 | } |