树链剖分 入门

什么是树链剖分?

  树链剖分说白了就是将树的节点按照某种顺序编号,使其在特殊的链上编号连续(类似区间),方便用数据结构维护。

如何树链剖分?

  树链剖分一般分为重链剖分和长链剖分,这里只介绍重链剖分(我也只会重链剖分)。

  重链剖分中有几个概念:

  1. 重儿子:一个节点的所有子节点中,以某个子节点为根的子树中节点数量最多的称为重儿子(如果最多的数量相同随便取一个)。
  2. 轻儿子:一个节点的所有子节点中,不是重儿子的节点就是轻儿子(这不是废话吗)。
  3. 重边:父节点与重儿子所连成的边。
  4. 轻边:父节点与轻儿子所连成的边。
  5. 重链:重边所连成的链。
  6. 轻链:轻边所连城的链。

如图所示,蓝色的边为重边,绿色的节点为重儿子。

树链剖分的第一步就是一遍dfs找出所有节点的重儿子,并处理出深度与父子关系(以后有用)。

代码如下:

void dfs(int x){
    size[x]=1,d[x]=d[fa[x]];//size表示节点数量,d表示深度
    for(int i=head[x];i;i=nxt[i]){
        int y=ver[i];
        if(y==fa[x]) continue;//fa表父节点
        dfs(y);
        size[x]+=size[y];
        if(size[y]>size[son[x]]) son[x]=y;//son表示重儿子
    }
}

树链剖分的第二步便是按照重儿子先遍历,轻儿子后遍历的次序再dfs一遍,给节点按照dfs序编号,还是如上面那张图所示,红色数字的代表重新编号后的编号。并处理出每一条重链的首端,轻儿子的首端为它自己。

代码如下:

void dfs(int x,top1){//top1代表重链首端
    top[x]=top1;//首端
    id[x]=++cnt;//重新编号
    if(!son[x]) continue;
    dfs(son[x],top1);//重儿子所在重链的首端和该节点一样
    for(int i=head[x];i;i=nxt[i]){
        int y=ver[i];
        if(y==fa[x] || y==son[x]) continue;
        dfs(y,y);//轻儿子所在首端为它自己
    }
}

处理完这两个dfs之后,我们可以发现,重链上的编号都是连续的!,这意味着我们可以像维护区间一样用数据结构(线段树、树状数组)维护每一条重链。我们还可以发现每个节点的儿子的子树编号也是连续的,用线段树描述的话区间[id[x],id[x]+size[x]-1]覆盖了以节点x为根的子树中所有节点。

当我们想要在树链上维护树上两个节点x,y之间的路径的数据时(列如节点权值和)或者求lca时,如果x与y所在重链的首端不同的话,一般都是将节点x与节点y同时向其所在重链的首端的父节点往上跳,因为重链上节点编号是连续的,所以我们可以在跳的过程中对每一条重链用数据结构进行维护。当重链首端相同的时候也就意味着它们在同一条重链,这时候维护节点x~y(或y~x,看节点深度)之间的信息就好了。

下面以“将节点x到节点y的最短路径上的所有节点的权值都加上a”为例给出代码。

int add(int x,int y,int a){
    while(top[x]!=top[y]){//重链所在首端不同的时候
        if(d[top[x]]<d[top[y]]) swap(x,y);//比较深度
        addtree(1,id[top[x]],id[x],a);//线段树维护链,维护重链首端top[x]到节点x之间的节点权值
        x=fa[top[x]];//往上跳至首端的父节点
    }  //此时首端相同
    if(d[x]>d[y]) swap(x,y);//看深度
    addtree(1,id[x],id[y],a);//维护此时x到y这条重链上的节点权值
}

两道例题:

    [SDOI2011]染色   题解
    [NO12015]软件包管理器   题解

对树链剖分的时间复杂度分析

  树链剖分的两个性质:

  1. 若(x,y)是一条轻边,那么 $size[y]<\frac{1}{2} size[x]$
  2. 从根结点到任意结点的路所经过的轻重链的个数必定都小于logn

第一个性质不难理解,可以用反证法。第二个性质因为如果某一个节点x所在的是重链,那么它可以直接跳到重链链头,所以我们考虑最坏情况,也就是从它到根节点的路上,它都走的是轻边,再考虑对树上每一个节点都只有两个size数量相同的子节点(因为如果不相同那更快),此时就和一棵二叉树差不多,走到根节点所经过节点数差不多是 $log_2n$(我是这么理解的,出了错我可是不负泽任的233)。

原文地址:https://www.cnblogs.com/Asika3912333/p/11417503.html

时间: 2024-11-03 22:35:39

树链剖分 入门的相关文章

树链剖分入门-Hdu3966 Aragorn&#39;s Story

AC通道:http://acm.hdu.edu.cn/showproblem.php?pid=3966 [题目大意] 一棵树上每个点有权值,每次支持三种操作:给[a,b]路径上的所有节点的权值加上k,给[a,b]路径上的所有节点的权值减去k,以及询问a的权值. [分析] 这是一道树链剖分模板题. 树链剖分,就是将树化成了许多链,将这些链用数据结构保存起来,再去维护这个数据结构. 假设给的树就是一条链,这道题当然很好办:直接将链用线段树存了,因为[a,b]的路径在线段树上也是连续的一段,那么修改一

HDU - 3966 Aragorn&#39;s Story(树链剖分入门+线段树)

HDU - 3966 Aragorn's Story Time Limit: 3000MS   Memory Limit: 32768KB   64bit IO Format: %I64d & %I64u Submit Status Description Our protagonist is the handsome human prince Aragorn comes from The Lord of the Rings. One day Aragorn finds a lot of ene

洛谷 P2590 树的统计 P3178 树上操作【树链剖分入门】

题目描述 一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w. 我们将以下面的形式来要求你对这棵树完成一些操作: I. CHANGE u t : 把结点u的权值改为t II. QMAX u v: 询问从点u到点v的路径上的节点的最大权值 III. QSUM u v: 询问从点u到点v的路径上的节点的权值和 注意:从点u到点v的路径上的节点包括u和v本身 输入格式: 输入文件的第一行为一个整数n,表示节点的个数. 接下来n – 1行,每行2个整数a和b,表示节点a和节点b之间有一条边相连

树链剖分入门

这几天学了一个树链剖分,觉得还不是很难,这里我试着讲一讲吧. 首先,我认为树链剖分是把在树上一个节点一个节点的走改为按照某种规则跳,从而降低了时间复杂度. 那这是什么规则呢? 首先我们得知道什么是重链,知道什么是重链就得先知道什么是重儿子,重儿子就是子树较大的儿子.然后对于一个点,我们总是往他的重儿子走,这样就构成了重链,那么剩下的就是轻链. 放张图直观些 然后我们同样可以对树进行dfs,只不过重儿子优先,这样我们也得到了一个dfs序,于是我们把树上问题成功转化成了线性问题.接着就可以用线段树等

spoj375 Query on a tree(树链剖分入门题)

#include <iostream> #include <algorithm> #include <cstring> #include <cmath> #include <queue> #include <map> #include <set> #include <vector> #include <cstdio> using namespace std; const int MAXN=10010

Hdu3966( Aragorn&#39;s Story ) 树链剖分

点更新树链剖分入门题,诶 错了好多发,提到同一点时没更新,边数组开小了一倍. #include <cstdio> #include <algorithm> #include <iostream> #include <string.h> using namespace std; #define lson l,mid,rt<<1 #define rson mid+1,r,rt<<1|1 const int maxn = 55555; in

树链剖分模板+入门题 SPOJ - QTREE

题目链接:[点击进入](http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=13013) 树链剖分并不是一个复杂的算法或者数据结构,只是能把一棵树拆成链来处理而已,换一种说法,树链剖分只是xxx数据结构/算法在树上的推广,或者说,树链剖分只是把树hash到了几段连续的区间上.比如说下面这道题,就是将树分为重链和轻链然后映射到线段树上,然后再在线段树上进行查询和修改等操作.所以树链剖分的重点有两个,一是正确的将树分解成几段并映射到

树链剖分(从入门到入土。)

前置知识:线段树,链式前向星,LCA,DFS序 树链剖分通常的操作: 1.x -> y 的路径上修改 2.x -> y 的路径上查询 3. 对于 x 的子树修改 4.对于 x 的子树查询. 一般还有换根操作.树剖也也可以做LCA. 树链剖分有两个DFS 这两个DFS就是把一棵树变成一个序列. 然后就可以用数据结构来维护了. 第一个DFS 用来求 \(fa\)(祖先节点) \(size\)(子树大小)\(son\)(重儿子) \(d\)(深度) 重儿子指的是\(size\)较大的儿子节点. 第二

bzoj-2243 2243: [SDOI2011]染色(树链剖分)

题目链接: 2243: [SDOI2011]染色 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 6267  Solved: 2291 Description 给定一棵有n个节点的无根树和m个操作,操作有2类: 1.将节点a到节点b路径上所有点都染成颜色c: 2.询问节点a到节点b路径上的颜色段数量(连续相同颜色被认为是同一段),如“112221”由3段组成:“11”.“222”和“1”. 请你写一个程序依次完成这m个操作. Input 第一行包含