树链剖分(heavy path decomposition,轻重路径剖分),我个人认为,相比起称之为一种“算法”,更像是一种处理树的相关问题时的一种方法,技巧。
其实,真正的树链剖分,应该是套个线段树进行维护的。不过,那个我并不会。所以这里写的是“树链”,真实面目其实应该是某种启发式合并。
首先,定义结点的
size
值为以该结点为根的子树的结点数。
给定一棵有根树,对于每个非叶子结点
u
,设
根据上面的定义,只需一次 DFS 就能把一棵有根树分解成若干重路径(重边组成的路径)和若干轻边。
路径剖分有两条重要定理:
- 对于
u 的子结点 v ,当(u,v) 为轻边时, size(v)<size(u)÷2 。
理由:根据定义,所有非叶结点往下都有一条重边。不妨假设 size(v)≥size(u)÷2 ,则对于 u 向下的重边(u,v′) 来说, size(v′)≥size(v)≥size(u)÷2 ,可得 size(u)≥1+size(v)+size(v′)≥size(u)+1 ,与假设矛盾。- 由定理 1 得,对于任意非根结点
u
,在
u 到根的路径上,轻边和重路径的条数均不超过 log2n ,因为每碰到一条轻边, size 值就会减半。 这样,在处理一类统计子树相关问题时,可以采用如下策略:
- 从所有叶子结点出发向上考察
- 每当到达一个结点时,要计算其各子树对当前总子树的答案贡献。对于重儿子(以重边连接的儿子),直接将其答案累加。对于轻儿子,dfs暴力统计。
对于重儿子,显然计算效率会比较高。但轻儿子的暴力统计呢?这样会不会导致超时?
其实是不会的。根据定理 2 可知,每个结点到根结点的路径上最多不会存在超过 log2n 条轻边,即,每个结点最多被统计 log2n 次。这样,总的时间复杂度其实只有 O(nlog2n) 。而对于真正的套上线段树的树链剖分,由于线段树的操作也是 log 级别的,因此复杂度还要再套一个 log,为 O(nloglogn) 。
直接这样讲会感觉很抽象,所以还是要通过例题来说明。详见《树》解题报告。
- 由定理 1 得,对于任意非根结点
u
,在