Day 2 - 分治、倍增、LCA 与树链剖分
1|0分治的延伸应用
1|1应用场景
优化合并
假设将两个规模
能直观的看出优化程度还是很大的。
归并排序中
特别的当
优化合并通常应用于哈夫曼树、线段树。
优化计数
我们对长度为
可以使用分治求解
我们令
代码实现的注意事项:
-
特判分治的递归边界。
-
若用到临时数组,确保其得到清空,切勿盲目
。
2|0倍增法
而
记
考虑一个
给定
令
状态转移方程为:
对于每个询问
两个部分的结果的较大值就是答案。
建议预处理对数数组:
除了
3|0最近公共祖先 LCA
3|1定义
最近公共祖先简称
为了方便,我们记某点集
3|2性质
本节 性质 部分内容翻译自 wcipeg,并做过修改。
; 是 的祖先,当且仅当 ;- 如果
不为 的祖先并且 不为 的祖先,那么 分别处于 的两棵不同子树中; - 前序遍历中,
出现在所有 中元素之前,后序遍历中 则出现在所有 中元素之后; - 两点集并的最近公共祖先为两点集分别的最近公共祖先的最近公共祖先,即
; - 两点的最近公共祖先必定处在树上两点间的最短路上;
,其中 是树上两点间的距离, 代表某点到树根的距离。
3|3求法
朴素算法
过程
可以每次找深度比较大的那个点,让它向上跳。显然在树上,这两个点最后一定会相遇,相遇的位置就是想要求的
或者先向上调整深度较大的点,令他们深度相同,然后再共同向上跳转,最后也一定会相遇。
性质
朴素算法预处理时需要
倍增算法
过程
倍增算法是最经典的
现在我们看看如何优化这些跳转:
在调整游标的第一阶段中,我们要将 1
的个数」次游标跳转。
在第二阶段中,我们从最大的
性质
倍增算法的预处理时间复杂度为
另外倍增算法可以通过交换 fa
数组的两维使较小维放在前面。这样可以减少
例题:HDU 2586 How far away? 树上最短路查询。
可先求出
参考代码:
Tarjan 算法
过程
- 首先接受输入边(邻接链表)、查询边(存储在另一个邻接链表内)。查询边其实是虚拟加上去的边,为了方便,每次输入查询边的时候,将这个边及其反向边都加入到
queryEdge
数组里。 - 然后对其进行一次
遍历,同时使用visited
数组进行记录某个结点是否被访问过、parent
记录当前结点的父亲结点。 - 其中涉及到了 回溯思想,我们每次遍历到某个结点的时候,认为这个结点的根结点就是它本身。让以这个结点为根节点的
全部遍历完毕了以后,再将这个结点的根节点设置为这个结点的父一级结点。 - 回溯的时候,如果以该节点为起点,
queryEdge
查询边的另一个结点也恰好访问过了,则直接更新查询边的 结果。 - 最后输出结果。
性质
朴素的
注意:
并不存在「朴素 find()
函数的时间复杂度为均摊
以下的朴素
3|4例题
例题 Luogu P3379【模板】最近公共祖先(LCA)。
参考代码:
4|0树链剖分
4|1树链剖分的思想及能解决的问题
树链剖分用于将树分割成若干条链的形式,以维护树上路径的信息。
具体来说,将整棵树剖分为若干条链,使它组合成线性结构,然后用其他的数据结构维护信息。
树链剖分(树剖/链剖)有多种形式,如 重链剖分,长链剖分 和用于
重链剖分可以将树上的任意一条路径划分成不超过
重链剖分还能保证划分出的每条链上的节点
如:
- 修改 树上两点之间的路径上 所有点的值。
- 查询 树上两点之间的路径上 节点权值的 和/极值/其它(在序列上可以用数据结构维护,便于合并的信息)。
除了配合数据结构来维护树上路径信息,树剖还可以用来
4|2重链剖分
我们给出一些定义:
定义 重子节点 表示其子节点中子树最大的子结点。如果有多个子树最大的子结点,取其一。如果没有子节点,就无重子节点。
定义 轻子节点 表示剩余的所有子结点。
从这个结点到重子节点的边为 重边。
到其他轻子节点的边为 轻边。
若干条首尾衔接的重边构成 重链。
把落单的结点也当作重链,那么整棵树就被剖分成若干条重链。
如图:
4|3实现
树剖的实现分两个
第一个
第二个
以下为代码实现。
我们先给出一些定义:
表示节点 在树上的父亲。 表示节点 在树上的深度。 表示节点 的子树的节点个数。 表示节点 的 重儿子。 表示节点 所在 重链 的顶部节点(深度最小)。 表示节点 的 DFS 序,也是其在线段树中的编号。 表示 序所对应的节点编号,有 。
我们进行两遍
4|4重链剖分的性质
树上每个节点都属于且仅属于一条重链。
重链开头的结点不一定是重子节点(因为重边是对于每一个结点都有定义的)。
所有的重链将整棵树 完全剖分。
在剖分时 重边优先遍历,最后树的
一颗子树内的
可以发现,当我们向下经过一条 轻边 时,所在子树的大小至少会除以二。
因此,对于树上的任意一条路径,把它拆分成从
4|5常见应用
路径上维护
用树链剖分求树上两点路径权值和,伪代码如下:
链上的
每次选择深度较大的链往上跳,直到两点在同一条链上。
同样的跳链结构适用于维护、统计路径上的其他信息。
子树维护
有时会要求,维护子树上的信息,譬如将以
在
每一个结点记录
这样就把子树信息转化为连续的一段区间信息。
求最近公共祖先
不断向上跳重链,当跳到同一条重链上时,深度较小的结点即为
向上跳重链时需要先跳所在重链顶端深度较大的那个。
参考代码:
怎么有理有据地卡树剖
一般情况下树剖的
于是我们可以考虑折中方案。
我们建立一颗
这样子我们可以将随机询问轻重链切换次数卡到平均
加上若干随机叶子看上去可以卡树剖。但是树剖常数小有可能卡不掉。
树链剖分求
4|6长链剖分
长链剖分本质上就是另外一种链剖分方式。
定义 重子节点 表示其子节点中子树深度最大的子结点。如果有多个子树最大的子结点,取其一。如果没有子节点,就无重子节点。
定义 轻子节点 表示剩余的子结点。
从这个结点到重子节点的边为 重边。
到其他轻子节点的边为 轻边。
若干条首尾衔接的重边构成 重链。
把落单的结点也当作重链,那么整棵树就被剖分成若干条重链。
如图(这种剖分方式既可以看成重链剖分也可以看成长链剖分):
长链剖分实现方式和重链剖分类似,这里就不再展开。
常见应用
首先,我们发现长链剖分从一个节点到根的路径的轻边切换条数是
如何构造数据将轻重边切换次数卡满
我们可以构造这么一颗二叉树 T:
假设构造的二叉树参数为
若
若
这样子构造一定可以将单独叶节点到根的路径全部为轻边且需要
取
长链剖分优化 DP
一般情况下可以使用长链剖分来优化的
我们可以考虑使用长链剖分优化树上
具体的,我们每个节点的状态直接继承其重儿子的节点状态,同时将轻儿子的
CF1009F
我们设
直接暴力转移时间复杂度为
我们考虑每次转移我们直接继承重儿子的
首先我们需要将重儿子的
然后我们将所有轻儿子的
注意到因为轻儿子的
也就是说,我们直接暴力合并轻儿子的总时间复杂度为
注意,一般情况下
例题参考代码:
当然长链剖分优化
参考 租酥雨的博客。
长链剖分求 k 级祖先
即询问一个点向父亲跳
首先我们假设我们已经预处理了每一个节点的
现在我们假设我们找到了询问节点的
我们考虑求出其所在重链的节点并且按照深度列入表格。假设重链长度为
同时我们在预处理的时候找到每条重链的根节点的
根据长链剖分的性质,
预处理需要倍增出
预处理复杂度
4|7练习
「洛谷 P3379」【模板】最近公共祖先(LCA)(树剖求 LCA 无需数据结构,可以用作练习)
「JLOI2014」松鼠的新家(当然也可以用树上差分)
「POI2014」Hotel 加强版(长链剖分优化 DP)
攻略(长链剖分优化贪心)
__EOF__

本文链接:https://www.cnblogs.com/So-noSlack/p/18291034.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
本文来自博客园,作者:So_noSlack,转载请注明原文链接:https://www.cnblogs.com/So-noSlack/p/18291034
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)