重链剖分
掌握树的重链剖分、路径查询和子树操作优化技术
概述
重链剖分是处理树上路径查询和修改的强大技术。通过将树分解为重链和轻边,将树上操作转化为序列操作,支持O(log²n)的路径查询、修改和LCA查询。
核心内容
1. 重链剖分基础
// 重链剖分实现
#include <iostream>
#include <vector>
using namespace std;
class HeavyLightDecomposition {
private:
int n, timer;
vector<vector<int>> adj;
vector<int> parent, depth, heavy, head, pos, subtreeSize;
// 第一次DFS:计算子树大小和重儿子
void dfs1(int v, int p, int d) {
parent[v] = p;
depth[v] = d;
subtreeSize[v] = 1;
int maxSize = 0;
for (int u : adj[v]) {
if (u != p) {
dfs1(u, v, d + 1);
subtreeSize[v] += subtreeSize[u];
if (subtreeSize[u] > maxSize) {
maxSize = subtreeSize[u];
heavy[v] = u;
}
}
}
}
// 第二次DFS:分配链头和位置
void dfs2(int v, int h) {
head[v] = h;
pos[v] = timer++;
// 优先遍历重儿子
if (heavy[v] != -1) {
dfs2(heavy[v], h);
}
// 遍历轻儿子,每个轻儿子开始新链
for (int u : adj[v]) {
if (u != parent[v] && u != heavy[v]) {
dfs2(u, u);
}
}
}
public:
HeavyLightDecomposition(int size) : n(size), timer(0) {
adj.resize(n);
parent.resize(n);
depth.resize(n);
heavy.assign(n, -1);
head.resize(n);
pos.resize(n);
subtreeSize.resize(n);
}
void addEdge(int u, int v) {
adj[u].push_back(v);
adj[v].push_back(u);
}
void build(int root = 0) {
dfs1(root, -1, 0);
dfs2(root, root);
}
// 查询路径上的所有区间
vector<pair<int, int>> getPathIntervals(int u, int v) {
vector<pair<int, int>> result;
while (head[u] != head[v]) {
if (depth[head[u]] > depth[head[v]]) {
result.push_back({pos[head[u]], pos[u]});
u = parent[head[u]];
} else {
result.push_back({pos[head[v]], pos[v]});
v = parent[head[v]];
}
}
// 处理同一条链上的部分
if (pos[u] > pos[v]) swap(u, v);
result.push_back({pos[u], pos[v]});
return result;
}
// 查询LCA
int lca(int u, int v) {
while (head[u] != head[v]) {
if (depth[head[u]] > depth[head[v]]) {
u = parent[head[u]];
} else {
v = parent[head[v]];
}
}
return depth[u] < depth[v] ? u : v;
}
// 获取子树区间
pair<int, int> getSubtreeInterval(int v) {
return {pos[v], pos[v] + subtreeSize[v] - 1};
}
// 获取节点在序列中的位置
int getPosition(int v) {
return pos[v];
}
// 获取节点深度
int getDepth(int v) {
return depth[v];
}
};
// 配合线段树的路径查询
class SegmentTree {
private:
vector<long long> tree, lazy;
int n;
void push(int node, int start, int end) {
if (lazy[node] != 0) {
tree[node] += lazy[node] * (end - start + 1);
if (start != end) {
lazy[2 * node] += lazy[node];
lazy[2 * node + 1] += lazy[node];
}
lazy[node] = 0;
}
}
void updateRange(int node, int start, int end, int l, int r, long long val) {
push(node, start, end);
if (start > r || end < l) return;
if (start >= l && end <= r) {
lazy[node] += val;
push(node, start, end);
return;
}
int mid = (start + end) / 2;
updateRange(2 * node, start, mid, l, r, val);
updateRange(2 * node + 1, mid + 1, end, l, r, val);
push(2 * node, start, mid);
push(2 * node + 1, mid + 1, end);
tree[node] = tree[2 * node] + tree[2 * node + 1];
}
long long queryRange(int node, int start, int end, int l, int r) {
if (start > r || end < l) return 0;
push(node, start, end);
if (start >= l && end <= r) return tree[node];
int mid = (start + end) / 2;
return queryRange(2 * node, start, mid, l, r) +
queryRange(2 * node + 1, mid + 1, end, l, r);
}
public:
SegmentTree(int size) : n(size) {
tree.resize(4 * n);
lazy.resize(4 * n);
}
void update(int l, int r, long long val) {
updateRange(1, 0, n - 1, l, r, val);
}
long long query(int l, int r) {
return queryRange(1, 0, n - 1, l, r);
}
};
// 树上路径操作
class TreePathOperations {
private:
HeavyLightDecomposition hld;
SegmentTree seg;
public:
TreePathOperations(int n) : hld(n), seg(n) {}
void addEdge(int u, int v) {
hld.addEdge(u, v);
}
void build(int root = 0) {
hld.build(root);
}
// 路径更新
void updatePath(int u, int v, long long val) {
vector<pair<int, int>> intervals = hld.getPathIntervals(u, v);
for (auto [l, r] : intervals) {
seg.update(l, r, val);
}
}
// 路径查询
long long queryPath(int u, int v) {
long long result = 0;
vector<pair<int, int>> intervals = hld.getPathIntervals(u, v);
for (auto [l, r] : intervals) {
result += seg.query(l, r);
}
return result;
}
// 子树更新
void updateSubtree(int v, long long val) {
auto [l, r] = hld.getSubtreeInterval(v);
seg.update(l, r, val);
}
// 子树查询
long long querySubtree(int v) {
auto [l, r] = hld.getSubtreeInterval(v);
return seg.query(l, r);
}
// LCA查询
int lca(int u, int v) {
return hld.lca(u, v);
}
};
int main() {
// 构建测试树
TreePathOperations tpo(7);
// 添加边
tpo.addEdge(0, 1);
tpo.addEdge(0, 2);
tpo.addEdge(1, 3);
tpo.addEdge(1, 4);
tpo.addEdge(2, 5);
tpo.addEdge(2, 6);
tpo.build(0);
// 路径更新
tpo.updatePath(3, 5, 10);
cout << "路径3到5的和: "
<< tpo.queryPath(3, 5) << endl;
// 子树操作
tpo.updateSubtree(1, 5);
cout << "子树1的和: "
<< tpo.querySubtree(1) << endl;
// LCA查询
cout << "节点4和6的LCA: "
<< tpo.lca(4, 6) << endl;
return 0;
}解题技巧
🎯 剖分理解
理解重链和轻边的概念,重链连接子树最大的儿子,轻边连接其他儿子。
⚡ 复杂度保证
每个节点到根路径最多经过O(log n)条轻边,保证了操作的对数复杂度。
🔧 实现要点
两次DFS分别计算重儿子和分配位置,配合线段树实现高效的区间操作。
🧮 应用场景
树上路径查询修改、LCA查询、子树操作,是处理树上问题的通用技术。