先让我们看一个题目
有一棵n个节点的树,树的每条边有个边权,有如下两种操作
1.修改一条边的边权
2.查询两点之间路径的权值
对于这种题目,可能有人会选择直接暴力,这很明显不行。
换一种思路,如果我们把树的每一条边拆下来,对他们进行编号,然后使用线段树来存储呢?使用线段树来对每条边的边权进行修改和查询是很方便的。于是这样我们就引出了树链剖分。
树链剖分其实就是把一棵树上的各个边拆开来进行处理,从而对树的整体进行划分。
将树划分为链,用数据结构来维护这些链,时间复杂度大致为O(log n) 。
在这里主要学习树链剖分里的轻重链剖分。
我们把树中的每一条边分为两种:轻边和重边
假设有一棵树U,我们用Size(U)来表示它的大小(即结点个数)。
令V为U所有子节点中大小最大的一个,则我们称边(U,V)为树U上的一条重边,其他的边均为轻边。V为重子节点,其他节点均为轻子节点。
下面引用IOI2009国家集训队论文漆子超《分治算法在树的路径问题中的应用》的一点内容来阐述轻重链剖分的性质:
性质1.如果边(U,V)为一条轻边,则有Size(V)≤ Size(U)/2
性质2.从根到某一点的路径上轻边的个数不会大于O(log n)
性质3.我们称某条链(原文中是路径)为重链,当且仅当这条链完全由重边组成。那么对于每个点到根的路径上都有不超过O(log n) 条轻边和O(log n) 条重链
让我们来证明一下这三条性质。
对于性质1,假设U有n个子节点,则V max 在二叉树的情况下是最大的,又是轻边,故Size(V)≤ Size(U)/2;
对于性质2,假设(U,V)是一条轻边,那么Size(V) max =Size(U)/2;要保证Size(V)≥ 1,那么最多也就向下拓展log n 的深度,最好情况下整条链为轻链,即完全由轻边构成,这时有log n 条轻边;
对于性质3,挂个图
很容易的可以看出树链剖分和树上分治的一点关系
在做树上分治的时候,我们是一次删除一条边(边分治)或者一个点(点分治),而在做树链剖分的时候我们一次删除一整条链,可以将其看做树上的链分治。通过这种方法,我们大大降低了在树上对边权进行操作的复杂度。
关于树链剖分各种情况下的复杂度,QuarterGeek的一篇文章有过列举
因为这东西本身没有功能,具体功能都是靠具体数据结构实现的,所以复杂度就是O(logn * ??)。如果套上了线段树,就是 O(log^2 n)。如果套上了树套树,那就是O(log^3 n)。如果你用树链剖分和树套树写区间第k大,那就是O(log^4 n)。
接下来我们简单介绍一下链剖的实现过程。
- 第一次DFS,在DFS拓展过程中求出每个节点的大小Size,深度Deep和祖先节点fa(用倍增的方法表示)。
- 第二次DFS,从根节点开始向下拓展构建重链。每次我们选择一个Size最大的子树继承上一次的重链,其他轻子节点单独构建新的重链。在DFS同时我们给每一个节点打上一个标号,这样节点就可以用来表示区间,使得每一条重链变成了一段区间,我们就可以将重链取下来放在线段树中维护。
- 权值的修改。单点权值的修改可以直接在树中完成操作。边权的修改我们要分两种情况讨论:假设我们要修改边(U,V)的边权。若U,V在同一条重链上,那么我们可以直接在线段树中修改区间[u,v] 的值;若U,V不在同一条重链上,我们可以用类似平衡树里提根的操作将他们向上提,直到两个点在同一条重链上,然后重复第一种情况的操作。
- 路径长度查询。与权值相似的,若U,V在同一条重链上,直接在线段树内查询;若不在一条重链上,提点然后查询。
写到这里已经很晚了= =没时间再去做一个例题写个模板上来了真是抱歉啊。题目什么的后面写解题报告里面吧。