哇!树链剖分(重链剖分学习笔记)

news/2024/10/24 13:38:30

听说有人不会树链剖分?

前置芝士

  • 线段树
  • 树状数组
  • Splay
  • FHQ-Treap

以上五种任意一种即可,这里主要讲线段树做法。

引入

树链剖分(Tree Line Pow Divide),一种解决树上快速路径修改查询问题的算法,一般指 重链剖分(Heavy Path Decomposition)。

思想图解

一个问题

如题,已知一棵包含 \(N\) 个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作:

  • 1 x y z,表示将树从 \(x\)\(y\) 结点简单路径上所有节点的值都加上 \(z\)

  • 2 x y,表示求树从 \(x\)\(y\) 结点简单路径上所有节点的值之和。

一些定义

顾名思义,重链剖分就是把”重“的链与其他的链分开,那么如何定义重呢?

我们定义一个 重儿子(Heavy Son)的概念:

以该点的儿子为根的子树中节点个数最多的儿子为重儿子

那么就可以递归定义 重链

若节点 u 为节点 v 的重儿子,那么 v 就归入到 u 所在的链中

否则,节点 v 就单独成为一条链的链首

那么一棵树可以被剖成这个样子:

一棵树

其中一个长方形代表一条链。

接下来即可定义一个 链顶 的概念:

一条链中,深度最低的点

深度,即

根节点到该节点的距离+1

思想概述

看到类似区修区查的语言:

  • 所有节点的值都加上 \(z\)

  • 所有节点的值之和。

不难想到用线段树(或树状数组等,下同);

但是难想的就是如何将一棵树剖成一个序列,从而使用线段树呢?

我们可以将树中的的节点重新编号,按照编号顺序建线段树,

其中编号序列满足以下条件(先不说为什么,待会再讲):

所有的重链的编号是连续的

是一种dfs序

这里我不想画图了,读者自己体会。

可以建树了,但是修改查询还不会。

修改

我们先考虑一种简单的情况:

情况A

修改的两个点 A,B 在同一条重链上:

一种情况

根据我们dfs序的建立,易证 A 到 B 的路径上的节点在线段树上一定是连续的。

那么就可以通过线段树的区间修改操作实现了。

\[\texttt{Change}(id_A,id_B,val) \]

这里就可以填上我刚才挖的那个坑了。

情况B
再引入一下

一个很好理解的定理(废话):

任意一个链顶不为根的链,链顶的父亲一定是另外一条链的一部分

同样,这里我不想画图了,读者自己体会。

接下来要讨论的情况就是不在同一条链上:

另一种情况

我们可以先把链顶深度较低的 B 所在的这条链的所有点修改了,并跳到其链顶的父亲所在的链的最后一个节点上;

\[\texttt{Change}(id_{top_B},id_B,val)\\ B=fa_{top_B} \]

接着同理修改 A;

\[\texttt{Change}(id_{top_A},id_A,val)\\ A=fa_{top_A} \]

即每次将链顶深度较低的往上爬,直到 A 与 B 重合。

其实,可以将两者结合一下:

每次将链顶深度较低的往上爬,直到 A 与 B 在同一条链。

施行情况A(因为 A 与 B 在同一条链上并不意味着 A = B)

查询

与修改大同小异,只是把 \(\texttt{Change}\) 换成 \(\texttt{Query}\) 而已,这里不赘述。

代码

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

int a[Maxn], tmp[Maxn];
int p;struct SegmentTree {//线段树
#define ls (id << 1)
#define rs (id << 1 | 1)struct Segment {int Left;int Right;int valMax;int tag;int valSum;} seg[Maxn << 2];il void PushUp(int id) {seg[id].valMax = max(seg[ls].valMax, seg[rs].valMax) % p;seg[id].valSum = (seg[ls].valSum + seg[rs].valSum) % p;return;}il void PushDown(int id) {if (seg[id].tag) {seg[ls].tag += seg[id].tag;seg[ls].tag %= p;seg[ls].valSum += seg[id].tag * (seg[ls].Right - seg[ls].Left + 1);seg[ls].valSum %= p;seg[rs].tag += seg[id].tag;seg[rs].tag %= p;seg[rs].valSum += seg[id].tag * (seg[rs].Right - seg[rs].Left + 1);seg[rs].valSum %= p;seg[id].tag = 0;}return;}il void Build(int id, int Left, int Right) {seg[id] = {Left, Right, 0, 0, 0};if (Left == Right) {seg[id].valMax = a[Left] % p;seg[id].valSum = a[Left] % p;return;}int mid = (Left + Right) >> 1;Build(ls, Left, mid);Build(rs, mid + 1, Right);PushUp(id);return;}il int QuerySum(int id, int Left, int Right) {PushDown(id);if (seg[id].Right < Left || seg[id].Left > Right) {return 0;}if (Left <= seg[id].Left && seg[id].Right <= Right) {return seg[id].valSum % p;}return (QuerySum(ls, Left, Right) + QuerySum(rs, Left, Right)) % p;}il void Change(int id, int Left, int Right, int val) {PushDown(id);if (seg[id].Right < Left || seg[id].Left > Right) {return;}if (seg[id].Left >= Left && Right >= seg[id].Right) {seg[id].tag += val;seg[id].tag %= p;seg[id].valSum += val * (seg[id].Right - seg[id].Left + 1) % p;seg[id].valSum %= p;return;}Change(ls, Left, Right, val);Change(rs, Left, Right, val);PushUp(id);return;}
};//以上内容不做解释vector<int> G[Maxn];//邻接表
int n, m, root;struct Qtree {//重链剖分struct treeNode {int fa;//该节点的父亲int son;//重儿子int dep;//深度int size;//字数节点个数int top;//该点所在链的链顶int tid;//重新编号的序号} tn[Maxn];SegmentTree SEG;int tot = 0;void dfs1(int step, int fa) {//初始化fa、dep、size、sontn[step].fa = fa;tn[step].dep = tn[fa].dep + 1;tn[step].size = 1;int Max = 0;for (auto x : G[step]) {if (x == fa) {continue;}dfs1(x, step);tn[step].size += tn[x].size;if (tn[x].size > Max) {//判重儿子Max = tn[x].size;tn[step].son = x;}}return;}void dfs2(int step, int top) {//初始化top、tidtn[step].top = top;tn[step].tid = ++tot;//有没有像Tarjan的dfn?a[tot] = tmp[step];//重新将点权赋值if (tn[step].son)//避免死循环dfs2(tn[step].son, top);//重儿子for (auto x : G[step]) {if (x == tn[step].fa || x == tn[step].son) {//排除重儿子continue;}dfs2(x, x);//因为x不是重儿子,所以x所在链的链首为自己}}void Build() {//建立dfs1(root, 0);//以root为根dfsdfs2(root, root);SEG.Build(1, 1, n);//以a数组建立线段树return;}void Change(int u, int v, int w) {while (tn[u].top != tn[v].top) {//u,v不在同一条链上if (tn[tn[u].top].dep < tn[tn[v].top].dep) {//简洁写法,即把链顶深度较低的点放到uswap(u, v);}SEG.Change(1, tn[tn[u].top].tid, tn[u].tid, w % p);//修改u = tn[tn[u].top].fa;//往上爬}if (tn[u].tid > tn[v].tid) {//最后执行情况Aswap(u, v);}SEG.Change(1, tn[u].tid, tn[v].tid, w % p);return;}int QuerySum(int u, int v) {//同理int Max = 0;while (tn[u].top != tn[v].top) {if (tn[tn[u].top].dep < tn[tn[v].top].dep) {swap(u, v);}Max += SEG.QuerySum(1, tn[tn[u].top].tid, tn[u].tid);Max %= p;u = tn[tn[u].top].fa;}if (tn[u].tid > tn[v].tid) {swap(u, v);}return Max + SEG.QuerySum(1, tn[u].tid, tn[v].tid);}
} Qt;

最后读者可以自行思考一下如何将边权转成点权,洛谷 P4114 Qtree1。

THE END

感谢 @Little_Cabbege、@qw1234321、@yinxiangbo2027 指出了本文的一些问题。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

某SCADA系统发电机过速故障研究

某SCADA系统发电机过速故障研究 直观上讲,发电机转速过高故障最显然的特征应该就是“发电机转速”,因此对故障发生时的发电机转速进行可视化研究:如上图所示,对发电机转速进行了 Min-Max 归一化。该次故障报警时,确实存在转速较高的情况,但显然,并非转速高就会报警。通过…

CANOpen协议SDO中止报文(内存不足的解决方法)

今天在开发过程中,使用SDO进行字符串传输的时候出现了错误,检查到SDO服务器返回的报文帧是一个中止帧,中止代码为0x05040005这时候去翻CIA301的手册查中止代码的含义为内存不足经过断点调试跟踪,发现在config.h中是一个配置宏设置的是32,而我的字符串的长度为50,所以就中…

WinDbg快速分析异常情况Dump文件

https://syxdevcode.github.io/2017/12/04/WinDbg%E5%BF%AB%E9%80%9F%E5%88%86%E6%9E%90%E5%BC%82%E5%B8%B8%E6%83%85%E5%86%B5Dump%E6%96%87%E4%BB%B6/WinDbg快速分析异常情况Dump文件 生产环境偶尔会出现一些异常问题,WinDbg 或 GDB 就是解决此类问题的利器。调试工具 WinDb…

20222317 2024-2025-1 《网络与系统攻防技术》实验三实验报告

一、实验内容 本次实验目的为通过多次加密、文件格式欺骗、填充、加壳等技术手段实现恶意代码免杀,产生恶意程序,并尝试通过杀毒软件,不被杀毒软件检测出来。具体实验内容如下: 1.正确使用msf编码器,使用msfvenom生成如jar之类的其他文件; 2.能够使用veil,加壳工具; …

EventTranscript.db占用空间太大,文件能否移动到其他位置?

在大多数情况下,EventTranscript.db 文件可以被移动到其他位置(不建议移动、删除),这样做可能会对系统日志记录功能产生影响:日志记录功能:移动 EventTranscript.db 文件可能会导致系统日志记录工具无法正常工作。系统完整性:在操作系统中,日志文件的位置是系统配置的一…

Windows下dump文件生成与分析

一 生成Dump文件 生成dump文件有三种方式:任务管理器生成,windbg抓取,源码中添加dump转储代码。需要根据实际情况选择。 1.1 任务管理器 在程序崩溃后,先不关闭程序,在任务管理器中找到该程序对应的进程。右键—>创建转储文件。 1.2 WinDbg抓取 程序运行崩溃后,先不关…

P5663 [CSP-J2019] 加工零件 题解

最短路对于上图,如果我们相知道 $2$ 号工人想要一个 $3$ 阶段的零件,其实是看 $2$ 到 $1$ 有没有一条长度为 $3$ 的路径.但如果要求 $4$ 阶段的路径,那就不一定了. 所以我们直接求一遍最短路,分奇最短路和偶最短路. 处理完后,最后一次 $\Theta (1)$ 的回答,如果路径长度过…

报error:0308010C:digital envelope routines::unsupported错--nodejs版本过高(nvm安装(更换)不同版本nodejs)

最近小编入职实习,运行(npm run dev)前端项目时报error:0308010C:digital envelope routines::unsupported的错,一查发现原来是nodejs版本过高,与项目不匹配。接下来介绍更换nodejs版本的方法。 第一种:官网下载通过nodejs官网下载安装 ,但有个缺陷,不同版本的nodejs无法顺…