Skip to content

Commit 912299b

Browse files
authored
Merge pull request #364 from hitonanode/hld-subtree
HLD: subtree クエリに対応
2 parents 7e6770d + e36a9b2 commit 912299b

9 files changed

+162
-87
lines changed

graph/extended_block_cut_trees.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ vector<vector<int>> bct_to = bct.to;
3232
assert(n + b == (int)bct_to.size());
3333

3434
// 得られた block cut tree を heavy-light decomposition などに使う場合
35-
HeavyLightDecomposition hld(bct.size());
36-
for (auto [i, j] : bct.get_edges()) hld.add_edge(i, j);
35+
heavy_light_decomposition hld(bct.size(), bct.get_edges());
36+
hld.build();
3737
```
3838
3939
## 問題例

graph/test/extended_block_cut_trees.yuki1326.test.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,11 @@ int main() {
1919

2020
const extended_block_cut_trees bct(N, edges);
2121

22-
HeavyLightDecomposition hld(bct.size());
23-
for (auto [i, j] : bct.get_edges()) hld.add_edge(i, j);
22+
heavy_light_decomposition hld(bct.size(), bct.get_edges());
2423
hld.build();
2524

2625
atcoder::fenwick_tree<int> fw(hld.V);
27-
for (int i = 0; i < N; ++i) fw.add(hld.aligned_id[i], 1);
26+
for (int i = 0; i < N; ++i) fw.add(hld.subtree_begin[i], 1);
2827

2928
int Q;
3029
cin >> Q;

tree/heavy_light_decomposition.hpp

Lines changed: 72 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -7,99 +7,118 @@
77
#include <utility>
88
#include <vector>
99

10-
// Heavy-Light Decomposition of trees
10+
// Heavy-light decomposition of trees (forest)
1111
// Based on http://beet-aizu.hatenablog.com/entry/2017/12/12/235950
12-
struct HeavyLightDecomposition {
12+
struct heavy_light_decomposition {
1313
int V;
1414
int k;
15-
int nb_heavy_path;
1615
std::vector<std::vector<int>> e;
1716
std::vector<int> par; // par[i] = parent of vertex i (Default: -1)
1817
std::vector<int> depth; // depth[i] = distance between root and vertex i
1918
std::vector<int> subtree_sz; // subtree_sz[i] = size of subtree whose root is i
2019
std::vector<int> heavy_child; // heavy_child[i] = child of vertex i on heavy path (Default: -1)
2120
std::vector<int> tree_id; // tree_id[i] = id of tree vertex i belongs to
22-
std::vector<int> aligned_id,
23-
aligned_id_inv; // aligned_id[i] = aligned id for vertex i (consecutive on heavy edges)
21+
// subtree_begin[i] = aligned id for vertex i (consecutive on heavy edges)
22+
std::vector<int> subtree_begin, subtree_end;
23+
std::vector<int> vis_order; // vis_order[subtree_begin[i]] = i
2424
std::vector<int> head; // head[i] = id of vertex on heavy path of vertex i, nearest to root
25-
std::vector<int> head_ids; // consist of head vertex id's
26-
std::vector<int> heavy_path_id; // heavy_path_id[i] = heavy_path_id for vertex [i]
27-
28-
HeavyLightDecomposition(int sz = 0)
29-
: V(sz), k(0), nb_heavy_path(0), e(sz), par(sz), depth(sz), subtree_sz(sz), heavy_child(sz),
30-
tree_id(sz, -1), aligned_id(sz), aligned_id_inv(sz), head(sz), heavy_path_id(sz, -1) {}
31-
void add_edge(int u, int v) {
32-
e[u].emplace_back(v);
33-
e[v].emplace_back(u);
25+
26+
heavy_light_decomposition(int n, const std::vector<std::pair<int, int>> &edges)
27+
: V(n), k(0), e(n), par(n), depth(n), subtree_sz(n), heavy_child(n), tree_id(n, -1),
28+
subtree_begin(n), subtree_end(n), vis_order(0), head(n) {
29+
for (auto [u, v] : edges) {
30+
assert(u != v);
31+
e.at(u).emplace_back(v);
32+
e.at(v).emplace_back(u);
33+
}
3434
}
3535

36-
void _build_dfs(int root) {
37-
std::stack<std::pair<int, int>> st;
36+
void _build_dfs_1(int root) {
37+
std::vector<std::pair<int, int>> st;
3838
par[root] = -1;
3939
depth[root] = 0;
40-
st.emplace(root, 0);
40+
st.emplace_back(root, 0);
4141
while (!st.empty()) {
42-
int now = st.top().first;
43-
int &i = st.top().second;
42+
int now = st.back().first;
43+
int &i = st.back().second;
4444
if (i < (int)e[now].size()) {
4545
int nxt = e[now][i++];
4646
if (nxt == par[now]) continue;
4747
par[nxt] = now;
4848
depth[nxt] = depth[now] + 1;
49-
st.emplace(nxt, 0);
49+
st.emplace_back(nxt, 0);
5050
} else {
51-
st.pop();
51+
st.pop_back();
5252
int max_sub_sz = 0;
5353
subtree_sz[now] = 1;
5454
heavy_child[now] = -1;
5555
for (auto nxt : e[now]) {
5656
if (nxt == par[now]) continue;
5757
subtree_sz[now] += subtree_sz[nxt];
58-
if (max_sub_sz < subtree_sz[nxt])
58+
if (max_sub_sz < subtree_sz[nxt]) {
5959
max_sub_sz = subtree_sz[nxt], heavy_child[now] = nxt;
60+
}
6061
}
6162
}
6263
}
6364
}
6465

65-
void _build_bfs(int root, int tree_id_now) {
66-
std::queue<int> q({root});
67-
while (!q.empty()) {
68-
int h = q.front();
69-
q.pop();
70-
head_ids.emplace_back(h);
71-
for (int now = h; now != -1; now = heavy_child[now]) {
66+
void _build_dfs_2(int now, int tree_id_now) {
67+
std::vector<std::pair<int, bool>> st;
68+
st.emplace_back(now, true);
69+
head[now] = now;
70+
71+
while (!st.empty()) {
72+
auto [now, mode] = st.back();
73+
st.pop_back();
74+
75+
if (mode) {
7276
tree_id[now] = tree_id_now;
73-
aligned_id[now] = k++;
74-
aligned_id_inv[aligned_id[now]] = now;
75-
heavy_path_id[now] = nb_heavy_path;
76-
head[now] = h;
77-
for (int nxt : e[now])
78-
if (nxt != par[now] and nxt != heavy_child[now]) q.push(nxt);
77+
subtree_begin[now] = k++;
78+
vis_order.push_back(now);
79+
80+
st.emplace_back(now, false);
81+
82+
for (int nxt : e[now]) {
83+
if (nxt == par[now] or nxt == heavy_child[now]) continue;
84+
head[nxt] = nxt;
85+
st.emplace_back(nxt, true);
86+
}
87+
88+
if (heavy_child[now] != -1) {
89+
head[heavy_child[now]] = head[now];
90+
st.emplace_back(heavy_child[now], true);
91+
}
92+
} else {
93+
subtree_end[now] = k;
7994
}
80-
nb_heavy_path++;
8195
}
8296
}
8397

8498
void build(std::vector<int> roots = {0}) {
8599
int tree_id_now = 0;
86-
for (auto r : roots) _build_dfs(r), _build_bfs(r, tree_id_now++);
100+
for (int r : roots) {
101+
_build_dfs_1(r);
102+
_build_dfs_2(r, tree_id_now++);
103+
}
87104
}
88105

89106
template <class T> std::vector<T> segtree_rearrange(const std::vector<T> &data) const {
90107
assert(int(data.size()) == V);
91108
std::vector<T> ret;
92-
ret.reserve(V);
93-
for (int i = 0; i < V; i++) ret.emplace_back(data[aligned_id_inv[i]]);
109+
ret.reserve(vis_order.size());
110+
for (int v : vis_order) ret.emplace_back(data[v]);
94111
return ret;
95112
}
96113

114+
void for_vertex(int u, const std::function<void(int)> &f) const { f(subtree_begin[u]); }
115+
97116
// query for vertices on path [u, v] (INCLUSIVE)
98117
void
99118
for_each_vertex(int u, int v, const std::function<void(int ancestor, int descendant)> &f) const {
100119
while (true) {
101-
if (aligned_id[u] > aligned_id[v]) std::swap(u, v);
102-
f(std::max(aligned_id[head[v]], aligned_id[u]), aligned_id[v]);
120+
if (subtree_begin[u] > subtree_begin[v]) std::swap(u, v);
121+
f(std::max(subtree_begin[head[v]], subtree_begin[u]), subtree_begin[v]);
103122
if (head[u] == head[v]) break;
104123
v = par[head[v]];
105124
}
@@ -112,7 +131,7 @@ struct HeavyLightDecomposition {
112131
const int lca = lowest_common_ancestor(u, v), dlca = depth[lca];
113132
while (u >= 0 and depth[u] > dlca) {
114133
const int p = (depth[head[u]] > dlca ? head[u] : lca);
115-
fup(aligned_id[p] + (p == lca), aligned_id[u]), u = par[p];
134+
fup(subtree_begin[p] + (p == lca), subtree_begin[u]), u = par[p];
116135
}
117136
static std::vector<std::pair<int, int>> lrs;
118137
int sz = 0;
@@ -121,28 +140,33 @@ struct HeavyLightDecomposition {
121140
if (int(lrs.size()) == sz) lrs.emplace_back(0, 0);
122141
lrs.at(sz++) = {p, v}, v = par.at(p);
123142
}
124-
while (sz--) fdown(aligned_id[lrs.at(sz).first], aligned_id[lrs.at(sz).second]);
143+
while (sz--) fdown(subtree_begin[lrs.at(sz).first], subtree_begin[lrs.at(sz).second]);
125144
}
126145

127146
// query for edges on path [u, v]
128147
void for_each_edge(int u, int v, const std::function<void(int, int)> &f) const {
129148
while (true) {
130-
if (aligned_id[u] > aligned_id[v]) std::swap(u, v);
149+
if (subtree_begin[u] > subtree_begin[v]) std::swap(u, v);
131150
if (head[u] != head[v]) {
132-
f(aligned_id[head[v]], aligned_id[v]);
151+
f(subtree_begin[head[v]], subtree_begin[v]);
133152
v = par[head[v]];
134153
} else {
135-
if (u != v) f(aligned_id[u] + 1, aligned_id[v]);
154+
if (u != v) f(subtree_begin[u] + 1, subtree_begin[v]);
136155
break;
137156
}
138157
}
139158
}
140159

160+
// query for vertices in subtree rooted at u
161+
void for_subtree(int u, const std::function<void(int, int)> &f) const {
162+
f(subtree_begin[u], subtree_end[u] - 1);
163+
}
164+
141165
// lowest_common_ancestor: O(log V)
142166
int lowest_common_ancestor(int u, int v) const {
143167
assert(tree_id[u] == tree_id[v] and tree_id[u] >= 0);
144168
while (true) {
145-
if (aligned_id[u] > aligned_id[v]) std::swap(u, v);
169+
if (subtree_begin[u] > subtree_begin[v]) std::swap(u, v);
146170
if (head[u] == head[v]) return u;
147171
v = par[head[v]];
148172
}
@@ -159,7 +183,7 @@ struct HeavyLightDecomposition {
159183
if (k < 0) return -1;
160184
while (v >= 0) {
161185
int h = head.at(v), len = depth.at(v) - depth.at(h);
162-
if (k <= len) return aligned_id_inv.at(aligned_id.at(v) - k);
186+
if (k <= len) return vis_order.at(subtree_begin.at(v) - k);
163187
k -= len + 1, v = par.at(h);
164188
}
165189
return -1;

tree/heavy_light_decomposition.md

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,39 +3,36 @@ title: Heavy-light decomposition (HLD, 木の重軽分解)
33
documentation_of: ./heavy_light_decomposition.hpp
44
---
55

6-
木の重軽分解を行い,列に対する処理を木上のパスに拡張した処理や,最小共通祖先の効率的な計算を可能にする.前処理 $O(N)$.
6+
木の重軽分解を行い,列に対する処理を木上のパスや部分木に含まれる全ての頂点に拡張した処理や,最小共通祖先の効率的な計算を可能にする.前処理 $O(n)$.
77

88
## 使用方法
99

1010
### 前処理
1111

1212
```cpp
13-
HeavyLightDecomposition hld(N);
14-
for (int e = 0; e < N - 1; ++e) {
15-
int u, v;
16-
cin >> u >> v;
17-
--u, --v;
18-
hld.add_edge(u, v);
19-
}
13+
int n; // 頂点数
14+
vector<pair<int, int>> edges;
15+
16+
heavy_light_decomposition hld(n, edges);
2017
hld.build(); // O(N)
2118
```
2219
2320
### 木に対する基本的なクエリ
2421
25-
以下,すべてクエリ $O(\log N)$.
22+
以下,すべてクエリ $O(\log n)$.
2623
2724
```cpp
2825
cout << hld.lowest_common_ancestor(a, b) << '\n'; // (a, b) の最長共通祖先
2926
cout << hld.kth_parent(a, k) << '\n'; // 頂点 a の k 世代親
30-
cout << hld.distance(a, b) << '\n'; // 2 頂点 a, b の距離
27+
cout << hld.distance(a, b) << '\n'; // 2 頂点 a, b の距離
3128
cout << hld.s_to_t_by_k_steps(s, t, k) << '\n'; // 頂点 s から t 方向に k 歩動いた頂点
3229
```
3330

3431
### セグメント木などを利用した木上パスクエリへの対応
3532

3633
```cpp
3734
// 各頂点の情報
38-
std::vector<S> vertices_info(N);
35+
std::vector<S> vertices_info(n);
3936

4037
// HLD の分解順を考慮したセグメント木の初期化
4138
// セグメント木上で,分解されたパス上の要素は「根方向のものから順に」連続して並ぶことに注意
@@ -50,3 +47,10 @@ hld.for_each_vertex(from, to, func);
5047
## 問題例
5148
5249
- [Library Checker: Lowest Common Ancestor](https://judge.yosupo.jp/problem/lca)
50+
- [Library Checker: Vertex Add Subtree Sum](https://judge.yosupo.jp/problem/vertex_add_subtree_sum)
51+
- [いろはちゃんコンテスト Day2 K - 虫取り](https://atcoder.jp/contests/iroha2019-day2/tasks/iroha2019_day2_k)
52+
- 部分木クエリとパスクエリの両方が使われる.
53+
54+
## 参考文献・リンク
55+
56+
- [Easiest HLD with subtree queries - Codeforces](https://codeforces.com/blog/entry/53170)
Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,25 @@
1+
#define PROBLEM "https://judge.yosupo.jp/problem/lca"
12
#include "../heavy_light_decomposition.hpp"
23
#include <iostream>
3-
#define PROBLEM "https://judge.yosupo.jp/problem/lca"
4+
using namespace std;
45

56
int main() {
7+
cin.tie(nullptr);
8+
ios::sync_with_stdio(false);
9+
610
int N, Q, p, u, v;
7-
std::cin >> N >> Q;
8-
HeavyLightDecomposition hld(N);
11+
cin >> N >> Q;
12+
vector<pair<int, int>> edges;
913
for (int i = 1; i <= N - 1; i++) {
10-
std::cin >> p;
11-
hld.add_edge(i, p);
14+
cin >> p;
15+
edges.emplace_back(i, p);
1216
}
17+
18+
heavy_light_decomposition hld(N, edges);
1319
hld.build();
1420

1521
while (Q--) {
16-
std::cin >> u >> v;
17-
std::cout << hld.lowest_common_ancestor(u, v) << "\n";
22+
cin >> u >> v;
23+
cout << hld.lowest_common_ancestor(u, v) << "\n";
1824
}
1925
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#define PROBLEM "https://judge.yosupo.jp/problem/vertex_add_subtree_sum"
2+
#include "../heavy_light_decomposition.hpp"
3+
#include <iostream>
4+
#include <utility>
5+
#include <vector>
6+
using namespace std;
7+
8+
#include <atcoder/fenwicktree>
9+
10+
int main() {
11+
cin.tie(nullptr), ios::sync_with_stdio(false);
12+
13+
int N, Q;
14+
cin >> N >> Q;
15+
vector<long long> A(N);
16+
for (auto &x : A) cin >> x;
17+
18+
vector<pair<int, int>> edges;
19+
for (int i = 1; i < N; ++i) {
20+
int p;
21+
cin >> p;
22+
edges.emplace_back(i, p);
23+
}
24+
25+
heavy_light_decomposition hld(N, edges);
26+
hld.build();
27+
28+
atcoder::fenwick_tree<long long> ft(N);
29+
30+
A = hld.segtree_rearrange(A);
31+
for (int i = 0; i < N; ++i) ft.add(i, A.at(i));
32+
33+
while (Q--) {
34+
int tp, u;
35+
cin >> tp >> u;
36+
if (tp == 0) {
37+
long long x;
38+
cin >> x;
39+
hld.for_vertex(u, [&](int i) { ft.add(i, x); });
40+
} else {
41+
long long ans = 0;
42+
hld.for_subtree(u, [&](int l, int r) { ans += ft.sum(l, r + 1); });
43+
cout << ans << '\n';
44+
}
45+
}
46+
}

tree/test/jump_on_tree_hld.test.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@ int main() {
77
cin.tie(nullptr), ios::sync_with_stdio(false);
88
int N, Q;
99
cin >> N >> Q;
10-
HeavyLightDecomposition tree(N);
10+
vector<pair<int, int>> edges;
1111
for (int e = 0; e < N - 1; ++e) {
1212
int a, b;
1313
cin >> a >> b;
14-
tree.add_edge(a, b);
14+
edges.emplace_back(a, b);
1515
}
1616

17+
heavy_light_decomposition tree(N, edges);
1718
tree.build({0});
1819

1920
while (Q--) {

0 commit comments

Comments
 (0)