【笔记】线段树合并 2025.3.5
【笔记】线段树合并 2025.3.5
参考资料
线段树合并:从入门到精通 - 题单 - 洛谷 | 计算机科学教育新生态
【笔记】数据结构专题 2023.8.5 - caijianhong - 博客园
【笔记】数据结构选讲 2025.2.10 - caijianhong - 博客园
数据结构专题-学习笔记:线段树合并 - Plozia - 博客园
题解 P4770 【NOI2018 你的名字】 - 洛谷专栏
《浅谈数据结构的合并与分裂》黄洛天
动态开点线段树
如果全局只有
但是如果全局有
我们引入动态开点线段树,一开始没有任何点,每次尝试访问一个节点时,如果它是空的,我们从未访问过它,则我们新建一个节点并记录相应的信息。由于节点的儿子可能是乱的,我们需要额外的空间记录每个节点的两个儿子的编号,或者将它们记为空。可以发现,由于正常线段树上每次操作的复杂度为
线段树合并(1)
如果有两棵不带懒标记的动态开点线段树,我们希望把它们合并。什么是合并?
例题 1
维护
个数组 ,每个 都是长度为 的数组,初始全为零。进行 次操作,每次操作为以下三种之一:
- 给定
,使 加上 。 - 给定
,对所有 进行 的操作,并将 标记为删除,以后不再访问 。 - 给定
,查询 。 强制在线。
。
如果没有 2 操作,我们开
如果有 2 操作,我们尝试线段树合并,将
而我们又知道,如果我们用动态开点线段树,线段树有
递归的进行这个过程,设合并两棵线段树,两边分别走到
- 如果
是空的,则返回 。 - 如果
是空的,则返回 。 - 如果
都是叶子,则将两边的 加起来,假如加到 上,就返回 。 - 否则
都不是叶子,合并 的左儿子和 的左儿子,作为新的 的左儿子;合并 的右儿子和 的右儿子,作为新的 的右儿子。对 做一次 pushup,返回 。
(这里画两棵小线段树模拟一下,
可以发现:
- 每次合并节点数不会变多只会变少。
- 每次合并遍历到的点只有被删除的点和它们的儿子,也就是遍历的点数比上被删除的点数为常数。
所以可以认为,线段树合并的复杂度为初始没有合并时的总点数。这个值刚好就是
线段树合并(2)
例题 2(P4556 [Vani有约会] 雨天的尾巴 /【模板】线段树合并 - 洛谷)
首先村落里的一共有
座房屋,并形成一个树状结构。然后救济粮分 次发放,每次选择两个房屋 ,然后对于 到 的路径上(含 和 )每座房子里发放一袋 类型的救济粮。 然后深绘里想知道,当所有的救济粮发放完毕后,每座房子里存放的最多的是哪种救济粮。
对于
测试数据,保证 , , 。
这个问题太难了,竟然要对树上的一条链进行操作。不过我们注意到信息是可以差分的,假如我们做树上前缀和和树上差分,那就将
在
线段树合并(3)
例题 3(P3605 [USACO17JAN] Promotion Counting P - 洛谷)
有一棵以
为根的有根树,每个点有点权 。对于每个节点 求出它子树中有多少个 满足 。
在每个节点上开一棵线段树,
线段树合并(4)
例题 4(CF600E Lomsat gelral - 洛谷)
- 有一棵
个结点的以 1 号结点为根的有根树。 - 每个结点都有一个颜色,颜色是以编号表示的,
号结点的颜色编号为 。 - 如果一种颜色在以
为根的子树内出现次数最多,称其在以 为根的子树中占主导地位。显然,同一子树中可能有多种颜色占主导地位。 - 你的任务是对于每一个
,求出以 为根的子树中,占主导地位的颜色的编号和。
与上一题一样,但是操作变成查询整棵树的最大值,这也是可以合并的。
线段树合并(5)
例题 5(P3521 [POI 2011] ROT-Tree Rotations - 洛谷)
给定一颗有
个叶节点的二叉树。每个叶节点都有一个权值 (注意,根不是叶节点),所有叶节点的权值构成了一个 的排列。 对于这棵二叉树的任何一个结点,保证其要么是叶节点,要么左右两个孩子都存在。
现在你可以任选一些节点,交换这些节点的左右子树。
在最终的树上,按照先序遍历遍历整棵树并依次写下遇到的叶结点的权值构成一个长度为
的排列,你需要最小化这个排列的逆序对数。 对于
的数据,保证 , ,所有叶节点的权值是一个 的排列。
交换左右子树会使左右子树之间产生的逆序对发生变化,而左右子树各自内部产生的逆序对不会发生变化。
可以计算
考虑如果有一棵完整的线段树,其中每片叶子有一个颜色黑或白,现在要计算所有黑叶子前面有多少片白叶子。我们在线段树上分治,强制要求用黑去查询白。假如当前到了线段树某个节点上,将答案分成:黑白都在左子树、黑白都在右子树、黑在右子树白在左子树,共三种情况,前两种自己递归下去,第三种可以维护每个节点子树内黑叶子和白叶子的数量,做一个乘法即可。以及有一些剪枝:如果子树全是黑的或者全是白的,就不用向下递归了,只有子树中又有黑又有白的时候尝试计算。
将刚才这个过程重新理解成线段树合并即可,可以发现需要计算的节点恰好是“又有黑又有白”的,刚好是两棵线段树的重叠部分。
线段树合并(6)
例题 6(CF208E Blood Cousins - 洛谷 或 P5384 [Cnoi2019] 雪松果树 - 洛谷)
有一个家族关系森林,描述了
( )人的家庭关系,成员编号为 到 。 如果
是 的父亲,那么称 为 的 级祖先;如果 有一个 级祖先, 是 的 级祖先的 级祖先,那么称 为 的 级祖先。 家庭关系保证是一棵森林,树中的每个人都至多有一个父母,且自己不会是自己的祖先。
如果存在一个人
,是两个人 和 共同的 级祖先:那么称 和 为 级表亲。
( )次询问,每次询问给出一对整数 和 ,求编号为 的人有多少个 级表亲。
相当于问 考虑离线,做 1 次 dfs 1 次长链剖分,复杂度线性。先解决树上
当然你发现将树拍成 dfs 序后,这题就能写成二维数点问题,可以写树状数组扫描线直接解决,这样就更好写。总的来说这题做法很多,线段树合并反而是最劣的一种做法。
线段树合并(7)
例题 7(P3899 [湖南集训] 更为厉害 - 洛谷)
设
为一棵有根树,我们做如下的定义:
- 设
和 为 中的两个不同节点。如果 是 的祖先,那么称“ 比 更为厉害”。 - 设
和 为 中的两个不同节点。如果 与 在树上的距离不超过某个给定常数 ,那么称“ 与 彼此彼此”。 给定一棵
个节点的有根树 ,节点的编号为 到 ,根节点为 号节点。
你需要回答个询问,询问给定两个整数 和 ,问有多少个有序三元组 满足:
为 中三个不同的点,且 为 号节点; 和 都比 更为厉害; 和 彼此彼此。这里彼此彼此中的常数为给定的 。
。
当然是先确定
是 的祖先,这一部分的贡献为 。 是 的祖先,这一部分在选定 后贡献为 ,需要将 拆开: ,这时 就有一个深度限制,然后 的贡献就和 有关。相当于要计算 在 的子树中, 的深度 ,求有多少个 以及它们的 的总和(这样才能计算 )。 ,相当于要计算 在 的子树中, 的深度 ,求有多少个 ,然后乘上 作为答案贡献。
这些问题可以用长链剖分解决,复杂度线性。这些问题用线段树合并解决,和上一题差不多,但是单点查询改成区间求和,时间复杂度
线段树合并(8)
例题 8(CF1009F Dominant Indices - 洛谷)
给定一棵以 1 为根,
个节点的树。设 为 子树中到 距离为 的节点数。 对于每个点,求一个最小的
,使得 最大。
显然,这是一道长链剖分题,使用长链剖分即可,复杂度线性。显然,这是一道线段树合并题,和前面的没有区别,复杂度
线段树合并(9)
例题 9(CF570D Tree Requests - 洛谷)
给定一个以 1 为根的
个结点的树,每个点上有一个字母( a
-z
),每个点的深度定义为该节点到 1 号结点路径上的点数。每次询问, 查询以 为根的子树内深度为 的结点上的字母重新排列之后是否能构成回文串。
判定一些字符能否重新排列为回文串,只需要出现次数为奇数的字符不超过一种即可。由于字符集太小,将每种字符的出现次数的奇偶性压缩成一个 int 即可,接下来就转化为了一道长链剖分题,使用长链剖分即可,复杂度线性。显然,这是一道线段树合并题,和前面的没有区别,复杂度
线段树合并(10)
例题 10(CF246E Blood Cousins Return - 洛谷)
给定一片森林,每个点有一个名字(字符串)。每次查询一个节点的子树中离他距离为
的点中有多少种不同的名字。 。
将所有名字离散化后有两种方法:第一种是将它写为平面线段数颜色,将平面一行一行地拍平成线段,然后做区间数颜色,复杂度 std::set
即可)的启发式合并,复杂度
还有一种想法:对于每个点,找出与它深度相同的,dfs 序在他前面的,颜色也与它相同的点(这个可以用 bfs 序
例题 6-10 总结
线段树合并如果上树的话,很多情况下可以改写为多维偏序或者长链剖分问题。如果能将其它维用线段树解决,且信息支持对位合并,就能无代价的将这个问题转化到子树上。但如果另一维是深度而且信息足够简单,建议直接写长链剖分;如果空间不足且支持离线,建议使用树上启发式合并(dsu on tree)或后文的线段树合并线性空间优化;还有最后一种方法,将树拍成 dfs 序,子树变为 dfs 序区间,将问题升高一维,用其它办法(例如可持久化线段树、K-D Tree、cdq 分治)等重新解决,但是这样会丧失潜在的优化的空间(增加了一个“自由度”)。
无法 pushup 的线段树合并
有的时候线段树是不支持 pushup 的。一个极端的例子是每个节点上是一棵平衡树,正常的修改都没有进行 pushup,那么线段树合并的时候就只能将两棵平衡树对位合并,这时变成平衡树合并复杂度分析,有另外一套算法。
带懒标记的线段树合并
有的时候如果有区间操作,则很有可能有懒标记,而每次下方懒标记的时候都有可能新建点,这样的情况下还能保证复杂度正确吗?
如果我们时刻保证:每个点要么没有儿子,要么有两个儿子,那么每次下放标记都不会新建节点。我们只需要关心一个没有儿子的节点怎么和有儿子的节点合并。强行下方没有儿子的节点的标记肯定是不行的,由于这个没有儿子的节点,它所代表的区间往往有一个统一的性质,该性质由懒标记提供,我们将这个标记直接打到有儿子的节点上就好了。这样的操作可能对懒标记的性质要求较高,比如要求懒标记之间可以交换。
“可持久化”线段树合并
例题 3(P4770 [NOI2018] 你的名字 - 洛谷 后面的线段树合并部分)
维护
个数组 ,每个 都是长度为 的数组,初始全为零。进行 次操作,每次操作为以下三种之一:
- 给定
,使 加上 。 - 给定
,对所有 进行 的操作,并保证此后不再对 进行操作 1, 2。 - 给定
,查询 。 强制在线。
。
首先需要思考为什么会有这样的问题,原来的算法难道不适用吗?确实不适用,虽然看起来
正确的做法是进行可持久化,这里的持久化操作比较简单,我们每次合并的时候不是合并到
可以发现经过这个持久化操作,复杂度不会发生变化。
注意你需要保证“此后不再对 这时变成真正的“可持久化线段树合并”,疑似图灵奖)。
线性空间线段树合并
例题(# 9605. 新生舞会 - Problem - QOJ.ac)
线段树合并,但是需要线性空间
引理 1:经过
考虑对线段树节点分类,
引理 2:对于任意一个结点
由于
考虑将合并的过程建树,对这个合并树做重链剖分,按照优先走重儿子的方法跑线段树合并。然后对线段树合并写垃圾回收。在节点
记(先假装下式是收敛的)
那么
所以
所以
*线段树合并维护树链的并
例题(CF1276F Asterisk Substrings - 洛谷 的线段树合并部分)
*李超线段树合并
李超线段树由于其惊人的结构,它的合并是特殊的。
复杂度:观察每条直线的深度。每次 merge 都需要一次 insert,且 merge 不会改变每条直线的深度,只有 insert 会改变,所以 merge 的复杂度不超过 insert。每次 insert 递归的时候,就会有一条直线的深度
*陈亮舟线段树合并
陈亮舟线段树(“楼房重建”线段树):P4198 楼房重建 - 洛谷
应用:【PR #15】二叉搜索树 - Problem - Public Judge
*线段树合并优化 dp
给定一棵树
和点对集合 ,满足对于所有 ,都有 ,并且 是 在树 上的祖先。其中 和 分别代表树 的结点集和边集。求有多少个不同的函数 : (将每条边 的 值置为 或 ),满足对于任何 ,都存在 到 路径上的一条边 使得 。由于答案可能非常大,你只需要输出结果对 (一个素数)取模的结果。 全部数据满足:
, 。输入构成一棵树,并且对于 , 始终为 的祖先结点。
本文来自博客园,作者:caijianhong,转载请注明原文链接:https://www.cnblogs.com/caijianhong/p/18754702
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
· 使用C#创建一个MCP客户端