线段树
Segment tree(线段树)
1.线段树的结构和思想
线段树基本结构

简单操作
1.单点修改:时间复杂度O(log n),因为线段树高是O(log n)的,然后会修改这个点到根的路径上的点权,所以是O(log n)的。
2.区间查询(比如:最小值)
实现
1 |
|
2.单点修改,区间查询(eg1)
1 |
|
2.1.【复杂的信息合并】最大子段和(eg2)
1 | //最大字段和 |
最大子段和变式
SP2916 GSS5 - Can you answer these queries V
题意:
给定一个序列。查询左端点在
思路:分类讨论。
第一种情况:没有两个区间没有相交部分。

第二种情况:有相交部分。

再分4种:
- ①+②+③
- ①+②
- ②
- ②+③
1 | //最大字段和 |
3.1【区间打标记,以及下传】区间加和区间最大值(eg3)
1 |
|
3.2【复杂的标记问题,标记的顺序】区间加,乘,赋值(eg4)
题意:给
支持
1 l r d
,令所有的加上 。 2 l r d
,令所有的乘上 。 3 l r d
,令所有的等于 。 4 l r
,查询。
思路:我们对于那么多操作,搞好几套标记显然是不太好的,那我们可以统一一下,把所有的
对于上述标记可以转化为:
①更新信息:
对于
原来总和是
②标记合并(有时间顺序的)
1 |
|
3.3有关端点的区间问题
[P6492 [COCI2010-2011#6] STEP]([P6492 COCI2010-2011#6] STEP - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) )
题意:
给定一个长度为 L
。
有 L
,则将 R
,否则将 L
。
对于一个只含字符 L
,R
的字符串 L
和 R
,则称
每次修改后,请输出当前序列
思路:
我们可以把L,R抽象为0、1
那么题目求的是最长01串(不存在重复的0和1)
因为不能有连续的0和连续的1,那我们在区间合并的时候就要考虑端点的值,如果一样就不能合并,否则可以,那么我们需要记录端点信息。要求最长满足的,根据最大字段和的那个题,思想差不多,记录前后缀和区间满足条件的最长长度。这里区间信息合并的时候要小心。
1 |
|
3.4 【权值线段树】乘法原理+线段树/树状数组
[P1637 三元上升子序列](P1637 三元上升子序列 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) )
题意:
在含有 thair
当且仅当
求一个序列中 thair
的个数。
思路:对于一个数,他能组成的thair
数等于它左边小于它的数的个数smaller[i]*它右边大于它的数的个数bigger[i]。
那么我们如何得知smaller[i]和biggr[i]数组呢?
我们想到之前写的逆序对的那个题,动态求解前缀和。动态更新并求解前缀和,正是树状数组的标准用法。
本题可以用两个树状数组维护或者用线段树。
1 |
|
3.5 区间加和区间赋值
[P1253 扶苏的问题](P1253 扶苏的问题 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) )
题意:
给定一个长度为
- 给定区间
,将区间内每个数都修改为 。 - 给定区间
,将区间内每个数都加上 。 - 给定区间
,求区间内的最大值。
思路:
本题很板,但需要注意的是区间加和区间赋值的标记下传顺序:应该是先赋值再区间加,因为如果反过来,后一个标记会覆盖前一个了。还要注意的是如果有区间赋值,区间加的标记会被清空。emmm,笨nannan我debug了好久。啊还有赋值操作要初始化为inf
1 |
|
3.6 有关二进制的线段树问题(拆位)
题意:
- 给定
个数的序列 。 次操作,操作有两种: - 求
。 - 把
异或上 。
- 求
, , , 。
思路:
因为是二进制按位操作,那么我们之前的写法维护节点信息是搞不了的,那怎么办呢?
我们对每个节点引入一个cnt[]数组,记录该节点二进制位的每一位有多少个1。
再看看修改操作。对于异或嘛,相同为0,相异为1。对于某一二进制位是0,如果异或0,那就不变,异或1就取反。如果某一二进制位i是1,异或0,还是1,异或1就取反。那么我们发现,无论这一位是0还是1,异或0就不变,异或1就取反,取反意味着0的个数变为1的个数,1的变0的。那我们cnt数组记录的是1的个数,如果取反就是sz-cnt[i],即节点大小-1的个数。
接下来对于询问,我们知道二进制位1的个数,那结果就是
总结:对于位运算,我们考虑一位一位来看。
1 |
|
3.7对于区间修改有极限情况的
1.区间取模:极限情况,区间最大值<mod
1 |
|
2.区间开根号:极限情况,区间最大值<=1
Can you answer these queries IV
1 |
|
3.8.DFS序+线段树+奇偶性分层
题意:给一颗n个节点的树,每个节点有点权,编号为1的节点是树的根。
有m次操作:
操作1:给x加上val,然后给x的所有子节点加上-val,x的子节点的子节点加上-(-val),不断传递。
操作2:查询点x的点权。
思路:
该题是关于子树的问题,可以先将树转化为dfs序,变成序列问题再用线段树解决。
本题难点在于,每次下传val的时候,要取反。
我们设d[x]为x到根的奇偶性,0/1表示。
在修改x的点权时,在以x为根的子树中,所有与x深度奇偶性相同的点+val,所有与x深度奇偶性不同的点-val。
根据上述分析,我们考虑把奇数层和偶数层分开,建2棵树。
在修改x的时候,与x奇偶性相同的树[L,R]添加val,这样做是为了将[L,R]中与x奇偶性相同的点全部加上val
并且与x奇偶性不同的树[L,R]减少val,这样做是为了将[L,R]中与x奇偶性不同的点全部加上val
奇数树中,深度为偶数的位置是没用的,直接忽略,偶数树同理。
1 |
|
4.线段树上二分(eg5)
给
支持
1 x d
,修改。 2 l r d
, 查询中大于等于 的第一个数的下标,如果不存在,输出 。也就是说,求最小的 满足 。
1 |
|
维护区间前k大之和(离线+权值线段树)
1 | // AC one more times |
5.区间最值操作(吉司机线段树)
[吉司机的ppt][[https://files.cnblogs.com/files/wawawa8/Segment_tree_Beats%21.pdf]
笼统地来说,区间最值操作指,将区间
eg1.Gorgeous Sequence
维护一个序列 a:
1.0 l r t,对于任意i属于l到r,a[i] = min(a[i],t);
2.1 l r 输出区间 [l,r] 中的最大值。
3.2 l r 输出区间和。
思路:
对于区间取min,意味着该区间内>t的数要变。也就是说,操作不是对整个区间,而是【区间内大于t的数】。
那么我们需要维护的信息是:区间最大值,次大值,区间和,区间最大值的个数。
TIPS:
我们可以换种方式来考虑:把线段树上每一个节点的最大值看成是区间取min标记,次大值看成是子树中标记的最大值。因为每次更新只会更新到(区间次大值<t<区间最大值)的情况,然后取修改max和sum。一旦进入子树,就必须先更新子树的sum和max,就需要先pushdown,把当前区间信息传给子树。
对于
- 如果区间最大值都比
小,那么整个操作无意义,直接 即可。 - 如果次大值比
小,最大值比t大,我们只需要更新区间最大值为 。并且更新区间和为 ,并打上一个标记。 - 如果次大值小于等于
,那我们不能确定有多少个要被更新。那我们就暴力递归下去(搜索它的子树),然后再 信息回来。
时间复杂度:
1 |
|
eg2(吉司机线段树模板题).BZOJ4695 最假女选手
给定一个长度为 N序列,编号从1 到 N。要求支持下面几种操作:
1.给一个区间[L,R] 加上一个数x
2.把一个区间[L,R] 里小于x 的数变成x
3.把一个区间[L,R] 里大于x 的数变成x
4.求区间[L,R] 的和
5.求区间[L,R] 的最大值
6.求区间[L,R] 的最小值
思路:
跟上一题同样的方法,我们取维护:最大mx,次大mx2,最小mn,次小mn2的值,和最大cmx和最小cmn的个数,区间和sum。还需要维护区间取min(tmn),取max和区间加的标记(tmx)。这里就会涉及到标记下传的顺序了。
策略:
- 认为区间加优先级最大,取min和max一样
- 对于某个节点加一个t标记,除了用t更新区间和信息,还需要用整个t更新区间取max和取min
- 对于一个节点取min,除了更新区间和信息,还有注意与区间max的标记比较。如果t小于区间max的标记,则最后所有数都会变成t,那么吧区间max的标记也变成t,否则不管。
细节:
- 注意取min和取max时候的特判,一个数可能即是最大值又是次小值这种(代码里有写)。
- 卡常,加快读
时间复杂度:
1 |
|
eg3.Mzl loves segment tree
两个序列
对
对
对
询问
每次操作完后,如果
思路:
先考虑最容易的区间加,因为只要加的不是
对于区间最值操作:
我们本质上将序列数分成三类:最大值、最小值、非最值进行维护。我们在打标记的时候顺便给
这种操作本质上就是把最值信息拿去给
eg4.CTSN loves segment tree
两个序列
对
对
对
对
询问区间的
我们把区间中的 位置 分成四类:在
6.历史最值问题
历史最值不等于可持久化
历史最值一般可分为三类:历史最大值、历史最小值、历史版本和。
历史最大值
简单地说,一个位置的历史最大值就是当前位置下曾经出现过的数的最大值。形式化地定义,我们定义一个辅助数组
这时,我们将
eg.P6242 【模板】线段树 3
eg.P4314 CPU 监控
序列 A,B 一开始相同:
- 对 A 做区间覆盖
- 对 A 做区间加
- 询问 A 的区间
- 询问 B 的区间
每次操作后,我们都进行一次更新,
思路:
我们先不考虑操作一,先只考虑区间加操作,我们维护区间加
因为要维护区间历史最大值,所以对每一个区间我们都需要维护一个新的值表示在更新这个区间的时产生的历史最大值,设为
对于生存周期:
对于一个标记会经历:
- 节点
被建立 - 节点
接受若干个新标记的同时与新标记合并 - 节点
标记下传给儿子, 的标记被清空
我们认为从
定义生存周期的意义?
因为一个节点的子节点在一个标记的生存周期内不会发生任何变化,并且保留这个周期之前的状态。因为在这个期间是没有标记下传的。
于是:当前标记生存周期内的历史
接下来,把操作一考虑进来:
对于区间覆盖操作,会把所有数变为同一个。因此我们可以把第一次区间覆盖后的所有标记都看成区间覆盖标记。
我们发现,对于一个点,首次赋值操作之后的任何修改都可以看作赋值操作,因为这样区间的所有数都相同了,若实现区间加,则直接看为对区间赋值的改变即可。
也就是说,一个标记的生产周期大致分为两个阶段:
- 若干个加减标记的合并,未接收过覆盖标记
- 覆盖标记(若干加减标记被转化为覆盖标记)
根据上述,于是我们把节点的
时间复杂度
1 |
|
- Title: 线段树
- Author: Nannan
- Created at : 2023-10-31 21:28:00
- Updated at : 2024-09-30 19:50:07
- Link: https://redefine.ohevan.com/2023/10/31/二、线段树Segment tree/
- License: This work is licensed under CC BY-NC-SA 4.0.