让我们先来看如下一个问题:
给定一棵n个节点的树,有如下两种操作:
1.修改树上一条边权值为v
2.查询树上两个节点间路径的距离
对于这个问题我们要怎么做?
暴力?妥妥的超时。
这样我们就引入了树链剖分的算法。
在上述题目中,虽然树的边权发生了改变,但是树的形态是没有发生任何变化的。因此我们可以将树上的链取下来存入数据结构中(线段树平衡树均可)利用这些数据结构的优越性能来实现权值的修改和路径距离的查询。
树链剖分有多种方式,使用最广泛的是轻重链剖分。
我们将树的节点、边和链分为轻重两种
记录Size(U)为节点U的大小(即子树节点个数),假定U有子节点V,V为U的所有子节点中大小最大的,那么V为重子节点,边(U,V)为重边。U的其他所有的子节点均为轻子节点,其他边相应的称为轻边。
完全由重边构成的一条路径叫做重链。
很显然,树链剖分有这样的性质(引自IOI2009国家集训队论文漆子超《分治算法在树的路径问题中的应用》):
1.如果(U,V)为轻边,则Size(V)
≤ Size(U)/2;
2.从根节点到任意其他某一节点的路径上,轻边的数量不会大于O(log n)
3.从根节点到任意其他某一节点的路径上,重链的数量不会大于O(log n)
下面我们来证明一下这三个性质
对于性质1:已知V为轻子节点,很显然Size(V)
对于性质2:轻边数量最多时,意味着这条路径是一条轻链,即路上所有点都是轻子节点,那么每个点的大小最多为其父节点大小的一半,又有该节点大小一定大于等于1,故轻边数量最多为
对于性质3:贴个图就看出来了
通常情况下,我们会将树中的所有链剖分出来,存在线段树里进行操作,那么这样整个程序的复杂度大致在
通过上述对链剖的学习,我们可以发现链剖其实和树的分治存在一定的关系,如果说我们平时说的树的分治是指点分治和边分治的话,链剖可以看做是“链分治”了,这也是为什么漆子超当年在自己《分治算法在树的路径问题上的应用》中为什么花了大半的篇幅来介绍链剖的原因。
再次借用漆子超的一句话来阐述链剖这个算法的一条特性:
“所以路径剖分算法
可以看做是基于链的分治,而且这种分治还有一个特点,每次被删除
结点的儿子必将作为下一次删除的链的头结点。这样,就给我们的维
护带来了方便。”
—————————————我是萌萌哒分割线—————————————
接下来就是链剖到底是怎么实现的了。代码实现可以放到后面其他文章里再提,我们先来看一下链剖过程的文字叙述吧。
进行权值的查询。与修改相似,仍然是基于线段树的单点查询、区间查询等基础操作。
值得一提的是,在我们进行链剖的预处理过程中其实已经顺便做完了LCA的操作,可以用来求点的公共祖先了。
在维护区间时,虽然通常我们是使用线段树的,但是根据题目的不同有时我们也会选择Splay。(大概也只有这两种了,因为只有他们能做区间)
具体的题目等以后慢慢做=。=再发新的文章吧
原文地址:http://blog.csdn.net/creationaugust/article/details/43792243