简介
树链剖分,一种对树进行划分的算法,它先通过轻重边剖分将树分为多条链。
保证每个点属于且只属于一条链,然后再通过数据结构(树状数组、SBT、SPLAY、线段树等)来维护每一条链。
它能巧妙地将树上信息映射到线性结构上(如套用线段树),应用广泛。
接下来,我们通过一个问题来探究树链剖分算法。
详解
【问题】
- 在一棵树上进行路径的修改、求极值、求和
【探究】
暴力计算固然可行,但当树退化成链时,查询操作就变成了 O(N) 了,显然超时。
发现操作与线段树类似,可是区间处理又成了问题。
【引入】
树链,就是树上的 路径。
剖分,就是把路径分类为 重链 和 轻链 。
树链剖分就是把一些点合成一条路径,使其在线段树中的编号(下标)有序,并用线段树来维护。
这样就可以使得查询、修改的效率大大提高。
假设我们把路径分好链了 (先不要在乎是怎么分的),每次询问两个点对 (x,y) 时,
若 x 和
y 在同一链中,直接 询问线段树中的 u 和v (因为同一条链中下标是连续的)其中 u ,
v 是 x ,y 对应的线段树中的点。若不在同一链中,我们从深度大的点上一点一点向上爬,
每次记录该点所在的链上的情况,直到 x ,
y 在同一条链上即可。注意:树链剖分中的线段树中每个 点 代表的意义可以是原图的 边 或 点 。
【设置数组】
设 size[v] 表示以 v 为根的子树的节点数,
dep[v] 表示 v 的深度(根深度为1),top[v] 表示 v 所在的链的顶端节点,fa[v] 表示 v 的父亲,son[v] 表示与 v 在同一重链上的v 的儿子节点(姑且称为 重儿子 ),tree[v] 表示节点 v 在线段树中的编号,
pre[v] 表示线段树中编号是 v 的节点所对应的原图中的点(与 tree 相反,互为反函数)只需把以上信息求出,即可
O(logN) 处理操作!
【术语解释】
重儿子 : size[u] 为 v 的子节点中
size 值最大的,那么 u 就是v 的重儿子。轻儿子 : v 的其它子节点。
重边 :点
v 与其重儿子的连边。轻边 :点 v 与其轻儿子的连边。
重链 :由 重边 连成的路径。
轻链 : 即 轻边。
【性质】
剖分后的树有如下 性质 (易证):
性质①: 如果
(v,u) 为轻边,则 size[u]∗2<size[v] ;性质②: 从根到某一点的路径上轻链、重链的个数都不大于 logN 。
【预处理算法实现】
我们可以用两个 dfs 来求出 fa、deep、size、son、top、tree、pre 。
dfs1 : 把 fa、deep、size、son 求出来,比较简单。
dfs2 :我们依次标记 tree[v] (按 dfs 序),同时得到 pre 。
对于 v ,当
son[v] 存在(即 v 不是叶子节点)时,显然有。(没有就直接退出)top[son[v]]=top[v] 然后我们先搜索 v 的 重儿子
u ,并把 u 的 重儿子、重孙子……的top 值也置为 top[v] ;接着我们再搜索 v 的轻儿子
u ,并把 u 的 重儿子、重孙子……的top 值置为 u ;
将树中各边的权值在线段树中更新,建链和建线段树的过程就完成了!
【查询&修改算法实现】
假设将
u 到 v 的路径上每条边的权值都加上x。记
f1=top[u],f2=top[v] 。当 f1<>f2 时,不妨设 dep[f1]≥dep[f2] ,那么就更新 u 到
f1 的权值(时间复杂度为 O(logn) ),并使 u=fa[f1] 。当 f1=f2 时, u 与
v 在同一条重链上,直接更新 u 到v 路径上的点的权值(时间复杂度为 O(logn) ),修改完成;重复上述过程,直到修改完成!
【模板题&附加代码】
- 以上问题选自 【BZOJ 2256】,参见我的博客:【BZOJ 2256】树的统计
总结
- 树链剖分算法可以有效而简便地处理树上问题,使算法优化很多!