首页 > 代码库 > 树的经典问题和方法
树的经典问题和方法
树的经典问题和方法
《算法竞赛入门经典(第2版)》392页
欧拉序列。对有根树t进行dfs(深度优先遍历),无论是递归还是回溯,每次到达一个结点时都将深度记录下来,可以得到一个长度为2n-1的序列,称为t的欧拉序列f(类似于欧拉回路)。
为了方便,把结点k在欧拉序列中第一次出现的序号记为pos(k)。
有了欧拉序列,lca问题可以在线性时间内化为rmq问题:lca(t,u,v)=rmq(b,pos(u),pos(v))。这里rmq返回的是下标而不是值本身。
这个等式不难理解:从u走到v的过程中一定会经过lca(t,u,v),但不会经过lca(t,u,v)的祖先。因此,从u走到v的过程中经过的深度最小的结点就是lca(t,u,v)。
用dfs计算欧拉序列的时间复杂度是o(n),且欧拉序列的长度是2n-1=o(n),所以lca问题可以在o(n)的时间内转化成等规模的rmq问题。
树的动态查询问题1。给定一棵带边权的树,要求支持两种操作:修改某条边的权值和询问树中两点间的距离。
首先把无根树变成有根树,则把一条边u-v(假定u是v的父亲结点)的权值增加d时,以u为根的整个子树的“到根节点的距离”同时增加d。不难发现,一棵子树内的结点对应欧拉序列中的一段连续序列,因此如果用dist[i]表示欧拉序列中第i个结点到根的距离,则修改操作就是dist数组上的“区间增量”,而查询时的距离(u,v)等于dist(u)+dist(v)-2dist(w),其中w=lca(u,v)。这样,只需用一个支持快速区间增量和单点查询的数据结构(例如fenwick树或者线段树)来维护dist数组,就可以在o(log n)的时间内支持两个操作。
轻重路径剖分。给定一棵有根树,对于每个非叶节点u,设u的子树中结点数最多的子树的树根为v,则标记(u,v)为重边,从u出发往下的其它边均为轻边。
根据上面的定义,只需一次dfs就能把一棵树分解成若干重路径(重边组成的路径)和若干轻边。有些资料也把重路径称为树链,因此轻重路径剖分也称树链剖分。
路径剖分最重要的定理如下:若v是u的子结点,(u,v)是轻边,则size(v)<size(u)/2,其中size(u)表示以u为根的子树中的结点总数。
证明并不复杂。由定义,所有非叶结点都有一条重边。假设size(v)>=size(u)/2,那么对于u向下的重边(u,w)来说,size(w)>=size(v)>=size(u)/2,因此size(u)>=1+size(v)+size(w)>=1+size(u),与假设矛盾。
由此可以得到如下重要结论:对于任意非根结点u,在u到根的路径上,轻边和重路径的条数均不超过log2n因为每碰到一条轻边size值就会减半。
树的动态查询问题2。给定一棵带边权的树,要求支持两种操作:修改某条边的权值和询问树中两点之间唯一路径上的最大边权。
首先把无根树变成有根树并且求出路径剖分。任意结点u到其根节点x的简单路径中包含一些轻边和重路径,但这些重路径可能并不是原树中的完整重路径,而是一些“片段”,因此可以在轻边中直接保存边权,而用线段树维护重路径。这样两个操作都不难实现。
修改:轻边直接修改,重边需要在重路径对应的线段树中修改。
查询:设lca(u,v)=p,则只需求出u到其祖先p之间的最大变权maxw(u,p),再用类似的方法求出maxw(v,p),则答案为max{maxw(u,p),maxw(v,p)}。为了求出maxw(u,p),依次访问u到p之间的每条重路径和轻边即可。根据刚才的结论,轻边和重路径的条数均不超过log2n。这样,修改的时间复杂度为o(log n),查询的时间复杂度为o(log2n)。虽然存在时间复杂度更低的方法,但上述方法已经很实用了。
树的经典问题和方法