数据结构复健 (四) : 树链剖分
学习计划
事项 | 详情 | 时间 |
---|---|---|
重温基本概念 | 参考课本与 OI-wiki | 1h |
基础题目练习 | 复习 P2590,P2146,P3178, 完成 P4427,ACMOJ1473 | 2.5h |
概念细节与拓展 | 课本, OI-wiki, 完成或学习 P1505 / P3979 | 2.5h |
思想
将一棵树划分为若干条链, 转化为线性结构, 从而用其它数据结构维护相关信息.
重链剖分可以将树上的任意一条路径划分成不超过 $O(\log n)$ 条连续的链, 每条链上的点深度互不相同 (即是自底向上的一条链, 链上所有点的 LCA 为链的一个端点) .
重链剖分还能保证划分出的每条链上的节点 DFS 序连续, 因此可以方便地用一些维护序列的数据结构 (如线段树) 来维护树上路径的信息.
重链剖分
重链剖分是最常用的树链剖分方式:
定义 重子节点 表示其子节点中子树最大的子节点. 如果有多个子树最大的子节点, 取其一. 如果没有子节点, 就无重子节点.
定义 轻子节点 表示剩余的所有子节点.
从这个节点到重子节点的边为 重边.
到其他轻子节点的边为 轻边.
若干条首尾衔接的重边构成 重链.
把落单的节点也当作重链, 那么整棵树就被剖分成若干条重链.
实现
运用到的数据结构为支持 单点/区间 修改/查询 的线段树, 记作
sjtu::SegmentTree<int, MAXN> stree;
首先进行两遍 dfs 建树
void dfs(int u, int last) {
int maxsiz = 0;
siz[u] = 1;
fa[u] = last;
for (int i = head[u]; i; i = edge[i].next) {
int v = edge[i].v;
if (v == last)
continue;
h[v] = h[u] + 1;
dfs(v, u);
siz[u] += siz[v];
if (siz[v] > maxsiz) {
maxsiz = siz[v];
mson[u] = v;
}
}
}
void build(int u, int last, int top) {
key[u] = ++ti;
tp[u] = top;
data[ti] = w[u];
if (mson[u])
build(mson[u], u, top);
for (int i = head[u]; i; i = edge[i].next) {
int v = edge[i].v;
if (v == last || v == mson[u])
continue;
build(v, u, v);
}
las[u] = ti;
}