学习笔记:重链剖分

news/2025/10/27 21:07:19/文章来源:https://www.cnblogs.com/-jz8-/p/-/hpd

本文写于 2025 年 9 月 28 日。

前言

这几天一下课,班里的某位同学就走到我座位旁边,念叨着“来学树剖”。在他的 传销 诈骗 怂恿 鼓励之下,我也终于来到了这座大山面前,但发现似乎也并不难……

何为重链剖分

考虑你有一棵树,对于树上的每个节点,在它的所有儿子中,我们称儿子(对于这个节点来讲,其实是孙子)数量多的儿子为重儿子,而称所有重儿子以外的儿子为轻儿子。我们称连接任意两个重儿子的边叫做重边,而其他边统称轻边。接下来就是重点:相邻重边连起来的,连接数个重儿子的链叫做重链。所谓重链剖分,就是将一棵树分成几条重链的过程。

重链剖分有何用

简而言之:重链剖分能帮助我们把要处理的数据从一个二维的树形结构转化为几个一维的线性结构,同时将树上的一些连续的顶点合并为一个部分,降低处理难度。此外,由于我们剖分的总是重链,也能一定程度上起到启发式的作用,降低时间复杂度。

如果你仍感到一知半解,那让我们看一道经典例题。

例题:P3384 【模板】重链剖分/树链剖分 - 洛谷

很明显地,这道题很容易能想到一个暴力解法:以操作 2 为例,我们可以先找到 \(x\)\(y\) 的最近公共祖先,然后从 \(x\) 一步步地访问到那个祖先,把沿途遇到的权值加进答案里,然后再从祖先那里一步步地访问 \(y\),并累加沿途的权值。

为什么我要把这个“一步步”加粗呢?因为这里就是导致我们 TLE 的罪魁祸首,也是我们进行优化的出发点。

不知道大家有没有因此联想到线段树,那也是把累加一个个数优化的过程。然而,显而易见地,线段树是在一个一维线性结构中进行的。想起来我们刚才提到的重链剖分的功能了吗?于是,我们就要用重链剖分,以列代树,并通过线段树解决这个问题。

由此衍生出一个疑问:线段树处理的是连续的区间,你怎么保证从 \(x\)\(y\) 的路径上经过的点是连续的呢?

这其实是无法保证的,因为我们事先不知道要处理哪些 \(x\)\(y\) 啊。不过,我们倒是可以把这一路径分成尽可能少且长连续区间。针对“连续”二字,我们可以想到 DFS 序,它使得树上连续的一段的编号也是连续的;而“少且长”三字,自然就对应“重链剖分”中的“重”字了。

回到操作 2,我们可以把这一区间修改操作分成几个步骤:

  1. 设所在链顶端深度更深的那个点为 \(x\) 点。
  2. \(\mathrm{ans}\) 加上 \(x\) 点到 \(x\) 所在重链顶端这一段区间的点权和。由于我们采用了 DFS 序,因此这些点的编号也是连续的,很容易通过线段树处理。
  3. \(x\) 跳到 \(x\) 所在链顶端的那个点的上面一个点。
  4. 重复执行第 2 步和第 3 步,直到两个点处于一条链上,这时再加上此时两个点的区间和。

可以看到,我们通过重链剖分,巧妙地将路径划分为了几条节点编号连续的重链,并将它们所得到的答案累加。以下是操作 2 的参考代码:

inline int queryPath(int x, int y) {int ans = 0;while (topOf[x] != topOf[y] && x != y) {if(deepOf[topOf[x]] < deepOf[topOf[y]]) {std::swap(x, y);}ans += tree.query(idOf[topOf[x]], idOf[x], 1, n, 1);ans %= MOD;x = parOf[topOf[x]];}if (deepOf[x] > deepOf[y]) {std::swap(x, y);}ans += tree.query(idOf[x], idOf[y], 1, n, 1);return ans % MOD;
}

对于剩余几个操作,方法也是类似的。

本题参考 AC 代码

#include <bits/stdc++.h>
typedef long long ll;
#define endl '\n'
const int N = 1e5+10;
std::vector<int> g[N];
int n, m, r, MOD, val[N], newVal[N];
int parOf[N], deepOf[N], sizeOf[N], maxSonOf[N], idOf[N], cnt, topOf[N];/* 线段树 */
struct Segtree{struct Node{ll val, tag;} tr[4*N];void build(int s, int t, int p) {if(s == t) {tr[p].val = newVal[s];return;}int mid = (s + t) >> 1;build(s, mid, p*2);build(mid+1, t, p*2+1);tr[p].val = tr[p*2].val + tr[p*2+1].val;}void pushDown(int s, int t, int p) {int mid = (s + t) >> 1;if(tr[p].tag) {tr[p*2].val += tr[p].tag * (mid - s + 1);tr[p*2+1].val += tr[p].tag * (t - mid);tr[p*2].tag += tr[p].tag;tr[p*2+1].tag += tr[p].tag;tr[p].tag = 0;}}int query(int l, int r, int s, int t, int p) {  if(l <= s && t <= r) {return tr[p].val;}int mid = (s + t) >> 1, sum = 0;pushDown(s, t, p);if(l <= mid) {sum += query(l, r, s, mid, p*2) % MOD;}if(r > mid) {sum += query(l, r, mid+1, t, p*2+1) % MOD;}return sum % MOD;}void update(int l, int r, int c, int s, int t, int p) {if(l <= s && t <= r) {tr[p].val += ((t - s + 1) * c) % MOD;tr[p].tag += c % MOD;tr[p].val %= MOD;tr[p].tag %= MOD;return;}int mid = (s + t) >> 1;pushDown(s, t, p);if (l <= mid) {update(l, r, c, s, mid, p*2);}if(r > mid) {update(l, r, c, mid+1, t, p*2+1);}tr[p].val = (tr[p*2].val + tr[p*2+1].val) % MOD;}  
} tree;/* 第一遍 DFS:获取各个节点的深度、父节点、子树大小和重儿子 */
void dfs1(int curr, int par, int deep) {deepOf[curr] = deep;parOf[curr] = par;sizeOf[curr] = 1;int maxSon = 0;for (int i = 0; i < g[curr].size(); i++) {int v = g[curr][i];if(v == par) continue;dfs1(v, curr, deep+1);sizeOf[curr] += sizeOf[v];if(sizeOf[v] > maxSon) {maxSonOf[curr] = v;maxSon = sizeOf[v];}}
}/* 第二遍 DFS:找出 DFS 序并进行重链剖分,即找出每个节点对应的重链起点 */
void dfs2(int curr, int top) {idOf[curr] = ++cnt;newVal[cnt] = val[curr];topOf[curr] = top;if(!maxSonOf[curr]) return;dfs2(maxSonOf[curr], top);for(int i = 0; i < g[curr].size(); i++) {int v = g[curr][i];if(v == parOf[curr] || v == maxSonOf[curr]) continue;dfs2(v, v); // 建立新链}
}/* 操作 2:查询路径点权和 */
inline int queryPath(int x, int y) {int ans = 0;while (topOf[x] != topOf[y] && x != y) {if(deepOf[topOf[x]] < deepOf[topOf[y]]) {std::swap(x, y);}ans += tree.query(idOf[topOf[x]], idOf[x], 1, n, 1);ans %= MOD;x = parOf[topOf[x]];}if (deepOf[x] > deepOf[y]) {std::swap(x, y);}ans += tree.query(idOf[x], idOf[y], 1, n, 1);return ans % MOD;
}/* 操作 4:查询子树点权和 */
inline int querySon(int x) {return tree.query(idOf[x], idOf[x] + sizeOf[x] - 1, 1, n, 1);
}/* 操作 1:修改路径点权 */
inline void updatePath(int x, int y, int k) {k %= MOD;while (topOf[x] != topOf[y] && x != y) {if (deepOf[topOf[x]] < deepOf[topOf[y]]) {std::swap(x, y);}tree.update(idOf[topOf[x]], idOf[x], k, 1, n, 1);x = parOf[topOf[x]];}if(deepOf[x] > deepOf[y]) {std::swap(x, y);}tree.update(idOf[x], idOf[y], k, 1, n, 1);
}/* 操作 3:修改子树点权 */
inline void updateSon(int x, int k) {tree.update(idOf[x], idOf[x] + sizeOf[x] - 1, k, 1, n, 1);
}signed main() {std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);std::cin >> n >> m >> r >> MOD;for(int i = 1; i <= n; i++) {std::cin >> val[i];val[i] %= MOD;}for(int i = 1; i <= n-1; i++) {int x, y;std::cin >> x >> y;g[x].push_back(y);g[y].push_back(x);}dfs1(r, 0, 1);dfs2(r, r);tree.build(1, n, 1);for (int i = 1; i <= m; i++) {int op, x, y, z;std::cin >> op;if (op == 1) {std::cin >> x >> y >> z;updatePath(x, y, z);} else if (op == 2) {std::cin >> x >> y;std::cout << queryPath(x, y) << endl;} else if(op == 3) {std::cin >> x >> z;updateSon(x, z);} else {std::cin >> x;std::cout << querySon(x) << endl;}}return 0;
}

结语

不知道这里该说些什么了,总之,重链剖分是个很有用的算法,希望大家都能掌握吧。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/948082.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

P3232 [HNOI2013] 游走

考虑贪心。 随机游走则显然每条边期望经过次数越大则其编号应越小。 每条边的期望经过次数难以计数,考虑每个店期望经过次数,设计状态 \(f_i\) 表示点 \(i\) 期望经过次数。 转移: \(f_i=\sum_{v\in e_i}f_v\cdot \…

FRP 后端无法获取请求者IP解决方案

FRP 后端无法获取请求者IP解决方案📝 FRP 后端无法获取请求者真实 IP?别慌,解决方案来啦!🧐 问题背景 当你使用 FRP 进行内网穿透或代理服务时,是否遇到过这样的困扰: 后端服务拿到的客户端 IP 全都是 FRP 服…

正睿 2025 NOIP 20连测 Day9

坐在前面的老哥 AK 了。他怎么这么成功,我怎么这么失败/ll。 T1小 W 有 \(n\) 个球,每个球都有一种颜色,其中第 \(i\) 个球初始的颜色可以用正整数 \(a_i\) 来表示,一共有 \(m\) 种颜色。 在一次染色操作中,小 W …

计算几何初步:CCW 与判断两线段的相交性

本文写于 2025 年 9 月 18 日。 前言 昨天在正睿的“IOI 普及联赛”中,有这样一道题,极大地冲击了本蒟蒻的心灵。赛后查看题解,此题竟然涉及计算几何,这更是本蒟蒻从未涉足的领域。我遂查询资料,学习了 CCW 算法以…

如何选择合适的团队共享网盘?坚果云、亿方云等15款产品横向测评

面对市场上琳琅满目的产品,管理者和技术决策者往往在可靠性、性能、安全性与成本之间难以取舍。本篇文章将围绕企业共享网盘,从功能覆盖、同步效率、安全合规、协作体验等维度,深度评测并对比市面上15款主流产品,帮…

软件工程学习日志2025.10.27

🎯 今日目标 完成基于Trae框架的IT岗位求职记录系统开发,重点训练数据库的增删改查操作能力 💻 项目启动:9:00 AM 技术选型确定 今天开始着手开发老师布置的IT岗位求职记录系统。经过技术调研,我决定采用以下技…

深入解析:TCP/IP 四层模型协作流程详解

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

Windows全版本激活教程(仅供测试)

Windows全版本激活教程(仅供测试)Windows 系统激活指南 注意:本文档及所提供的工具仅供学习、测试和环境验证使用。请确保您遵守软件许可协议,在合法范围内使用。 免责声明 使用任何激活工具都存在潜在风险,包括但不…

基本概念2

1, 访问控制列表步骤一:创建一个访问控制规则 步骤二:调用这个规则基本配置和高级配置 基本配置只能限制源地址,不检查目标地址 高级配置源地址与目标地址都检查步骤一: 命令: acl name test advance # test是给…

20251027周一日记

20251027周一日记前些日: 1.周五玩得挺爽,熬到五点多睡的,转天有点遗憾但还是玩得挺爽。见识到了大城市之间的差距。 2.周六回家,出去吃饭;周日在家,出去吃饭。听家人说各有各在拼搏的方向。 今日: 1.早上睡过了…

【通讯协议】IIC

前言 对于各种协议的知识,假如不常用的话还是很容易忘记和生疏的,于是我会在这篇文章重新学习,顺带记录一下,以便下次复习。由于最近要用到 IIC,所以最先复习 IIC 的内容。 网上有关 IIC 的资料很多也很全面,所以…

Robot Queries

题目传送门 前置知识——向量的加减 \((x_1,y_1) \pm (x_2,y_2) = (x_1\pm x_2,y_1\pm y_2)\)。 满足交换律和结合律。 题目大意 有一个在 \((0,0)\) 的点。现在给出 \(n\) 个操作序列 \({f}\),每个指令形如 \((x, y)…

10月27日

今天上午学了统一建模语言和数构,下午学了Java

特殊的数字签名

盲签名 -- 部分盲签名 -- 群签名盲签名 Chaum盲签名协议 协议流程: \[\begin{flalign} &Setup:\\ &\quad p,q = getPrime(safe.bit\_length);n = p * q;Pubkey = (n, e);Pravitekey = d\\ &Sign:\\ &…

CSP-S 40(爆零记)

10.2710.27 赤到了。 第一次爆蛋。 t1 特判没卡掉11个人。 乐死了。 暴力有80pts。 正解: 发现值域很小只有1000,从此入手。 先预处理 1000 以内的素数,发现很少只有168个,空间可开下,这启发我们对于每个素数记录…

javascript构造对象数组向服务器端传输

javascript构造对象数组向服务器端传输客户端发送数据1 $("#saveEnable").click(function () {2 var selectedRows = $(#userTab).datagrid(getSelections);3 4 var users…

102302136 林伟杰 数据采集与融合作业1

目录作业一实验过程及结果-1 心得体会-1作业二实验过程及结果-2 心得体会-2作业三实验过程及结果-3 心得体会-3作业一: 实验过程及结果-1要想爬取到网站中大学的信息,应当先查看该网站中html的结构,通过搜索框搜索&…

TCP/IP协议概述

TCP/IP分层模型是互联网协议套件的基础,它简化了OSI模型,将网络通信过程划分为四个层次。TCP/IP模型的主要目的是提供一个实际可行的网络通信架构,它是互联网和许多其他网络的基础。TCP /IP,是一组不同层次上的多个…

极值定理

若函数 \(f\) 在 \(x = c\) 处有一个局部最大值或局部最小值,则 \(f\) 在 \(x = c\) 处不可导或者 \(f(c)=0\) 。 比如说 \(f\) 在 \(x = c\) 处是个尖角,那么肯定就不可导。 若可导,不妨令是最大值(最小值类似)则…

10.25 CSP-S 模拟赛

Contest CSP-ST1 你脑子呢? 确定的情况即选比 \(a_i\) 小的,记 \(a_i\) 的排名为 \(rank_i\),则答案为 \(\binom{rank_i - 1}{k - 1}\)。 T2 大力分讨。 无论什么情况都有一个直接走到的选项 \(\operatorname{lcm}(…