全叶农 发表于 2025-8-9 20:47:55

重链剖分

重链剖分

定义

重链剖分是树链剖分的一种解决方式,通过树链剖分将树划分为多个连续部分方便执行操作
我们进行如下定义:

[*]son 表示 u 结点的重儿子。对于重儿子, 我们定义在 u 的所有子树中最大的子节点,且重儿子只有一个
[*]sz 表示 u 结点的子树大小, 包括结点 u 本身
[*]dep 表示 u结点的深度
[*]dfn 表示 u 结点所表示的 dfs 序
[*]top 表示 u 结点所在链的顶点
[*]fa 表示 u 结点的父节点
我们对一棵树进行如下划分,对于每个结点 u ,将其重儿子称为重子结点,而由重儿子所组成的一条链成为重链,其余节点成为轻儿子,特别的,重子节点一定会组成一条链,因为一个节点只存在一个重儿子,所以任何重子结点不存在两个及以上的重子节点。
通过以上分化后,我们可以通过 dfs 对每个结点定义其 dfs 序,对于这个遍历过程,我们采用重儿子优先的策略,优先向重儿子进行递归,可以保证重链的 dfs 序是连续的,并且对于任意一结点 u ,保证其子树内的 dfs 序大于 u,并且不超过 dfn + sz - 1
总结就是:

[*]重儿子所组成的一定是一条链
[*]每个节点至多有一个重儿子,满足每个结点均在一条重链上
[*]重链的 dfs 序是连续的
[*]子树 u 的 dfs 序范围是 , dfn + sz - 1]
应用

通过以上定义,我们可以通过线段树快速对一个子树内的所有元素进行统一修改,因为一个子树内的所有节点的 dfs 序一定连续
同时,我们还可以快速查询树上最近公共祖先 LCA ,我们可以知道,树上任意一个点 u 都必然存在于一个重链之上,所以两个点只要不断跳到其所在链的顶点,再不断上跳,就能快速找到最近公共祖先;还可以对树上的一条路径进行操作
示例

以洛谷题目 P3384 【模板】重链剖分为例
初始化

dfs1 :计算每个子树的大小,以及每个节点的深度,父亲,以及重儿子
void dfs1(ll u, ll father) {
        fa = father;
        dep = dep + 1;
        sz = 1;
        for (auto v : g) {
                if (v == father) continue;
                dfs1(v, u);
                sz += sz;
                if (sz > sz]) son = v;
        }
}dfs2:计算每个节点的 dfs 序,以及每个链的链顶
void dfs2(ll u, ll topf) {
        top = topf;
        dfn = ++timer;
        rk = u;//rk在这里是一个逆数组,用于标记每个dfn所对应的结点,用于后续操作
        if (son) dfs2(son, topf);
        for (auto v : g) {
                if (v != fa && v != son)
                        dfs2(v, v);
        }
}线段树模板:

//线段树维护的是dfs序对应的数组 a],因此树上的子树或路径问题可以转化为数组的区间问题。struct SegmentTree {        void push_up(ll p) {                tree = (tree, dfn + sz - 1, y);                }                if (op == 4) {                        cin >> x;                        cout
页: [1]
查看完整版本: 重链剖分