树链剖分笔记

树论总结中的树链剖分一部分新建一个帖子。

树链剖分是处理树上问题的重要工具,用于将树分割成若干条链的形式,以维护树上的路径信息。

对树链剖分的学习由一道题开始:

对一棵有 n n n 个节点,节点带权值的静态树,进行三种操作共 q q q 次:

  1. 修改单个节点的权值
  2. 查询 u u u v v v 的路径上的最大权值
  3. 查询 u u u v v v 的路径上的权值之和。

保证 1 ≤ n ≤ 30000 1 \leq n \leq 30000 1n30000 1 ≤ q ≤ 200000 1 \leq q \leq 200000 1q200000


于是就诞生了树链剖分

树链剖分笔记 \Huge树链剖分笔记 树链剖分笔记


目录:

  1. 树链剖分的作用。
  2. 树链剖分的种类。
  3. 树链剖分的相关定义。
  4. 树链剖分的实现。
  5. 树链剖分两个 D F S DFS DFS 的代码。
  6. 树链剖分的性质。
  7. 树链剖分的常见应用。

现在开始介绍树链剖分

  1. 树链剖分的作用:
    • 需要一个强大的能够对树上的数据统一处理的工具,于是有些很烦的人发明了树链剖分。
    • 具体来说,将整棵树剖分为若干条链,使它组合成线性结构,然后用其他的数据结构维护信息
  2. 树链剖分的种类:
    • 树链剖分具有多种形式,如:
      • 重链剖分
      • 长链剖分
      • 实链剖分
    • 大多数情况下(没有特别说明),树链剖分指的都是重链剖分,这也是最常用的。
  3. 树链剖分的相关定义:
    • 重子节点:对于一个节点,重子节点就是其子节点中子树最大的子节点。有多个最大就任取一,没有子节点就无重子节点。
    • 轻子节点:对于一个节点,轻子节点是除重子节点外的剩余所有子节点
    • 重边:从一个节点到它的重子节点的连边
    • 轻边:从一个节点到其它轻子节点的连边
    • 重链:若干条首尾相连的重边组成的链
    • 把落单的节点也当作重链,那么整棵树就被剖分为若干条重链。

若不理解的话可以看下图:
树链剖分定义示意图.png

  1. 树链剖分的实现:
    • d f s 1 dfs1 dfs1第一次 D F S DFS DFS
      • 记录每一个节点的父节点
      • 记录每一个节点的深度
      • 记录每一个节点的子树大小
      • 记录每一个节点的重子节点
    • d f s 2 dfs2 dfs2第二次 D F S DFS DFS
      • 记录每一个节点的链顶(即所在重链的顶),应当初始化为节点本身
      • 记录每一个节点在重边优先遍历的时候的** D F S DFS DFS序**。
      • 记录 D F S DFS DFS序所对应的节点编号(与上一点相互映射)。
  2. 树链剖分两个 D F S DFS DFS的代码:
    • f a x fa_x fax x x x的父亲。
    • d e p x dep_x depx x x x的深度。
    • s i z x siz_x sizx x x x的子树大小。
    • s o n x son_x sonx x x x的重儿子。
    • t o p x top_x topx x x x的链顶。
    • d f n x dfn_x dfnx x x x D F S DFS DFS序。
    • r n k x rnk_x rnkx D F S DFS DFS序对应的节点编号,因此 r n k d f n x = x rnk_{dfn_x}=x rnkdfnx=x
void dfs1(int o) {
    son[o] = -1;
    siz[o] = 1;
    for (int j = h[o]; j; j = nxt[j])
        if (!dep[p[j]]) {
            dep[p[j]] = dep[o] + 1;
            fa[p[j]] = o;
            dfs1(p[j]);
            siz[o] += siz[p[j]];
            if (son[o] == -1 || siz[p[j]] > siz[son[o]]) son[o] = p[j];
        }
}

void dfs2(int o, int t) { 
    top[o] = t;
    cnt++;
    dfn[o] = cnt;
    rnk[cnt] = o;
    if (son[o] == -1) return;
    dfs2(son[o], t); // 优先对重儿子进行 DFS,可以保证同一条重链上的点 DFS 序连续
    for (int j = h[o]; j; j = nxt[j])
        if (p[j] != son[o] && p[j] != fa[o]) dfs2(p[j], p[j]);
}
  1. 树链剖分的性质:
    • 树上的每个节点都属于且仅属于一条重链。
    • 在剖分时重边优先遍历,最后树的 d f n dfn dfn 序上,重链内的 d f n dfn dfn 序是连续的,按 d f n dfn dfn 序排序后的序列即为剖分后的链。
    • 一颗子树内的 d f n dfn dfn 序是连续的。
    • 可以发现,当我们向下经过一条轻边时,所在子树的大小至少会除以 2 2 2。因此,对于树上的任意一条路径,把它拆分从 l c a lca lca 往两边走,分别最多走 O ( l o g   n ) O(log~n) O(log n) 次,因此,树上的每条路径都可以被拆分成不超过 O ( l o g   n ) O(log~n) O(log n) 条重链。
  2. 树链剖分的常见应用:
    • 路径上维护。例如用树链剖分求树上两点路径权值和,由于链上的 d f s dfs dfs 序是连续的,可以使用线段树、树状数组维护。每次选择深度较大的链往上跳,直到两点在同一条链上。这样跳链的操作适用于维护,统计,修改路径上的其他信息。
    • 子树维护。有时会要求,维护子树上的信息,比如将以 x x x 为根的子树的所有节点的权值增加 。由于子树中节点的 d f s dfs dfs 序是连续的。那么子树信息就可以转化为一段区间信息。
    • 最近公共祖先:不断向上跳重链,当跳到同一条重链上时,深度较小的点即为 l c a lca lca

终于结束了

容易发现经过重链剖分,一段重链的节点和一个子树的节点 dfs 序都是连续的,因此可以直接用线段树等维护区间信息的数据结构维护。——2023.7.20

既然来了又滑到底部,看到这里还不赞一个

  • 51
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值