树链剖分
算法简介
¶树链剖分是将树上的节点映射到一段连续的区间中,使得树上任意一条路径能用不超过
算法原理
¶ : 以 为根的子树的节点数 : 的子节点中 值最大的节点- 重边:
与 的连边 - 轻边:
与除 外其它子节点的连边 - 重链: 由重边连成的路径
定理1
¶如果
因为
是一条轻边,所以 还有一个由重边相连的儿子 .
若,根据定义,有 .
与矛盾。
故,。
定理2
¶任意非根节点
不难证明,最多会遇到
条轻边。
因为从根节点到的路径中,每遇到一条轻边,节点个数就会减半,
所以轻边的数目不超过.
而整条路径上条轻边最多隔开 条重链。
故,轻边+重链 总数不超过。
由 定理2 可知当我们将一棵树沿着重链剖分后,将重链依次映射到一段连续的区间后,就可以将任何一条到根的链分成
算法实现
¶ : 的父亲 : 的子节点中 值最大的节点 : 的深度,根节点深度为 : 以 为根的子树的节点数 : 在连续区间的映射值 : 所在重链的顶端节点
求出
dfs1.cpp123456789101112int fat[MAXN], son[MAXN], dep[MAXN], siz[MAXN];void dfs(int o, int f, int d) {fat[o] = f; son[o] = 0;dep[o] = d; siz[o] = 1;for (int u = from[o]; u; u = nxt[u]) {int v = to[u];if (v == f) continue;dfs(v, o, d+1);siz[o] += siz[v];if (siz[son[o]] < siz[v]) son[o] = v;}}求出
,dfs2.cpp12345678910111213141516int top[MAXN], pos[MAXN], dfs_clock;void dfs(int u, int t) {pos[u] = ++dfs_clock;top[u] = t;if (!son[u]) return;// u 与 son(u) 在同一条重链上,故重链的顶端节点相同dfs(son[u], t);for (int i = from[u]; i; i = nxt[i]) {int v = to[i];// u 的其它非 son(u) 子节点为新的重链的顶端节点if (v != fat[u] && v != son[u]) dfs(v, v);}}
树链剖分完了!!怎么用呢?如下图所示,粗线表示重边,虚线表示轻边,黑色数字为节点编号,紫色数字为边编号。图中的 0
节点为一虚拟节点,引进它是方便理解下文
[1]。每条边上的紫色数字表示箭头所指的节点的

对于树上的两个节点
不妨假设
- 若
,则得到一段连续的映射区间 ;让 走到 - 否则
,即 和 在同一条重链中。显然, ,得到一段连续的映射区间
由于每次走到 节点6
--> 节点15
的路径:
走到 12
,得到一段连续的映射区间走到 1
,得到一段连续的映射区间走到 2
,得到一段连续的映射区间走到 1
,得到一段连续的映射区间,得到一段连续的映射区间 ;相遇,终止算法
所以,我们在映射区间里依次对
12345678910111213141516void update(int L, int R) { while (top[L] != top[R]) { // 让 dep 大的走 if (dep[top[L]] < dep[top[R]]) swap(L, R);
// 得到连续映射区间 [l,r] int l = pos[top[L]], r = pos[L];
// 对 [l,r] 进行操作 fun(l, r);
L = fat[top[L]]; } if (dep[L] > dep[R]) swap(L, R); fun(pos[L], pos[R])}
小记
¶上文中讨论的是对
Related
¶