重链剖分小结

Posted by 汤键|兔子队列 on January 3, 2022 禁止转载
本文总共 1307 字 · 阅读全文大约需要 4 分钟

基本概念

  • 重子节点:所有子节点中子树大小最大的儿子节点
  • 轻子节点:除重儿子之外的所有子节点
  • 重边:重子节点和其父节点之间连的边
  • 轻边:轻子节点和其父节点之间连的边
  • 重链:重边连接而成的链
  • dfs序:对树进行深度优先搜索,第一次访问到某个点时对其标号,按标号从小到大排序得到的序列叫dfs序
  • dfs序是能够反映树上的点连续的信息的,所以链就是dfs序

树链剖分的优点

  • 求LCA中的优化无非就是减少无效搜索的次数
  • 树链剖分把树拆成不同的链(保证不重不漏)
  • 因为两个点的LCA一定在一条链上(同一个点)
  • 所以如果搜索到的点不在同一条链上,那么不是倍增跳或是······,而是跳过一整条链
  • 这样搜索的速度会大幅提高
  • 我们知道,在dfs序里,许多点分成了好多段
  • 比如这里有一棵树:我们假设它的dfs序为:abdhiejcfg
  • 那么对于一条路径a-h,它的dfs序是连续的
  • 如果我们要更改他们的信息,我们就很好的利用了线段树区间修改的优点
  • 但如果我们要更改i-g的信息,我们发现这条路径 i-d-b-a-c-g 在dfs序中并不连续,甚至在这里我们必须单点修改
  • 完全没有发挥线段树区间维护的优势
  • 在这里我们得到了一个启示:
  • 我们尽量要让尽可能多的点在dfs序中连续
  • 这样我们修改时才会尽可能地发挥线段树区间维护的优势
  • 很明显,我们可以做到划分
  • 这个就是重儿子轻儿子的概念
  • 重儿子:以该儿子为根的子树所含节点数是其他儿子中最多的
  • 轻儿子:除重儿子之外就是轻儿子
  • 所以我们在DFS中,优先搜索重儿子,这样,我们就能让更多的节点在dfs序中连续

为什么讲树链剖分求LCA

  • 因为链上修改是基于求LCA操作的
  • 相比于树链剖分求LCA,链上修改只是加了个线段树的操作而已
  • 关于LCA
  • 我们运用top[i]表示i所在重链链的深度最浅的节点编号
  • 对于轻链上的点i的top[i]就是它本身i同时用id[i]表示i节点在dfs序中的位置
  • 这样,id[top[i]]~id[i]的区间就是一条链上的点了
  • 如top[11]-11点在dfs序上是连续的
  • 于是我们可以直接修改这一段区间的信息,都让它们加1

一般来讲,树链剖分可以解决以下问题

  • 1.修改两点路径上各点的值
  • 2.查询两点路径上各点的值
  • 3.修改某点子树上各点的值
  • 4.查询某点子树上各点的值
  • 5.求解LCA问题

树上修改/查询

  • 以某一个节点为根的子树编号是这个节点+这个节点的size[]-1
  • 即新树映射到数列上,一棵子树所占的区间是:[id[x],id[x]+size[x]−1]

正文

  • 例题及注释
  • 重链剖分的代码可以概括为:DFS+线段树+LCA
  • DFS是为了处理信息
  • 线段树和LCA是为了解决问题
  • 首先进行2次DFS进行初始化
  • 先看DFS1
  • 第一次,我们处理出重儿子和其他易于统计的信息
  • 第二次,我们按照重儿子优先的原则去dfs并记下dfs序和节点编号的对应关系,顺便处理链上信息
  • 至此我们就处理完了所有需要的信息,成功剖树为链
  • 然后上线段树
  • 然后看DFS2,每个点遍历子节点时优先遍历的是重儿子
  • 这说明每条重链的dfs序上的位置是连续的一段
  • 而每一次在树上移动是直接移动到链头
  • 这就可以对这一条重链上的信息区间修改或者查询
  • 直接把dfs序架在线段树上就能实现了
  • 通过爬lca时每次跳链头来区间修改或查询就完成了