树链剖分【产品说明书】

一种暴论:树链剖分 = 多叉树上分块 + 线段树


适用范围

总之就是数据结构的基础问题。
总的来说,树链剖分可以在\(O(m\log n)\)的时间复杂度中,解决大多数树上路径问题,包括其修改、维护和查询。

例如这样的一道模板题

又例如……(请直接跳到本文最后一章)


产品简介

树链剖分有两种:重链剖分,长链剖分。(按照剖分树的方式区分)

重链剖分中有如下一些定义:

重儿子:除叶子节点外,每个点的儿子中,子树最大(即子树节点最多)的子节点。(每个节点只有一个)
轻儿子:除叶子节点外,每个点的儿子中,不是重儿子的节点。(每个节点可能有不止一个)
重边:除叶子节点外,两端点都是重儿子的边。它用于构成重链。
轻边:除叶子节点外,每个节点到其轻儿子的边。它起到连接重链的作用。
重链:一些重边连成的链。
轻链:一些轻边连成的链。

而长链剖分,相当于将重链剖分中“子树大小”的部分替换为了“子树深度”,例如:

长儿子:除叶子节点外,每个点的儿子中,子树最深(即子树的层数最大)

综上,树链剖分是对树进行剖分,最终使其变成一些线性且不相交的链的算法。这时候,对树的操作,就变成了在链上的操作。
链可是经常作为部分分存在的好东西啊


生效原理

关于它的性质和实现方式。

树链剖分的性质

每个节点只能属于一条链,如果每次都选择节点更多的分支建链,建出的链自然会更长,从而,链的数量也就会更少。

由此,对于重链剖分,有这样的性质:
从根节点到任意一个叶子只需要经过\(O(\log_{2}n)\)条重链。
(因为每条轻边意味着树的大小至少缩小了一半。所以整棵树中,轻边的数量是\(O(\log_{2}n)\)的。而两条重链必然由轻边连接。)

它的另一个性质是:每条重链的DFS序是连续且有序的。
这一性质使重链得以被线段树维护。对于树上区间的修改查询问题,它“继承”了线段树的优势。

树链剖分的实现

详见代码解析(指路


使用范例

DFS序与线段树

我们知道两个性质:

  1. 每条重链内的DFS编号连续且有序
  2. 每棵子树内的DFS编号连续且有序

所以以DFS编号建立线段树。则同一条重链的节点,在线段树上是一个连续区间。

路径上的修改与查询

假设给出节点\(x\)\(y\),要修改/查询\(x\)\(y\)最短路径上节点的权值。

这其实类似于求LCA的过程。(毕竟树上最短路径嘛)而这个过程又类似于ST表求LCA的过程。
不妨假设\(x\)链头深度更深,设\(dfn[u]\)表示节点\(u\)的dfs编号。

修改

首先,令\(x\)沿重链向上跳到链头。修改这段路径中的节点权值。(对应线段树中区间为\([dfn[x], dfn[链头]\)

然后将\(x\)跳到上方的重链,并再次执行上一操作。重复这两步,直到\(x\)\(y\)的链头深度相同。

然后将\(x\)\(y\)同时向上跳(类似上两步的方法),直到它们处于同一条重链之中。此时再修改\(x\)\(y\)之间的节点即可。(对应线段树中区间为\([dfn[x], dfn[y]]\)

过程中的每次修改,直接调用update函数即可。

查询

路径查询和路径修改一模一样。只需要把update函数换成query函数。
(甚至可以用同一个求LCA函数实现查询&修改)

子树里的修改与查询

子树里的操作相比于路径更加简单。
一棵子树中节点的DFS序是连续且有序的,所以可以直接用线段树进行操作(当然,提前处理子树的\(l\)\(r\)



完结撒花!
posted @ 2023-10-12 20:53  _kilo-meteor  阅读(5)  评论(1编辑  收藏  举报