线段树
普通线段树
普通的线段树先不谈,我们来看一点比较高级的科技。
-
直接给一些好题:
线段树分治
啊啊啊今天突然发现全机房只有我不会线段树分治。。。
有时我们会在时间轴上进行一些操作,或者清除我们之前的操作。这时候我们可以使用这样一种思路:直接在时间轴上建立线段树,然后以类似标记永久化的形式把所有操作(因为有清除的存在,所以每个操作都会有他生效的一个区间)放进线段树里(对应区间),最后对这棵线段树进行\(DFS\),进入一个点的时候就把影响统计进来,回溯的时候去掉影响。
显然每个询问都是在叶子节点上的,我们只要在访问到叶子的时候统计一下就行了。
-
例题
线段树合并/分裂
一般用于权值线段树(至少我见过的)。
-
合并
考虑合并两棵线段树上的对应节点,有两种情况:
- 有一棵树是空的,直接返回另一棵非空树
- 两棵树都非空,合并这两个节点,并递归左右子树
稍微思考可以发现,线段树合并并不能保证复杂度,因为显然合并两棵满线段树的时间复杂度是\(O(n)\)。所以线段树合并一般使用在动态开点并且初始节点很少的题目中。使用前一定要注意复杂度分析。
-
分裂
要求你从权值线段树中以第\(k\)小的点为界,前面的属于原树,后面的把分离出来成为一棵新树。这样把一棵线段树分裂成两棵的过程,叫做线段树分裂。
这里我们再多记一个\(siz[x]\)表示节点\(x\)内有多少数。现在我们要以第\(k\)个数为界分裂\(x\)节点,设为\(Split(x,k)\),继续考虑分类讨论(下面\(l(x),r(x)\)分别表示\(x\)的左、右节点,并设分离出来的新树为\(y\)):
- \(siz[l(x)]<k\),则左子树依然还是\(x\)的,递归右子树\(Split(r(x),k-siz[l(x)])\)
- \(siz[l(x)]==k\),则正好左边是\(x\)的,右边都归\(y\),结束
- \(siz[l(x)]>k\),则右子树全部归\(y\),递归左子树\(Split(l(x),k)\)
注意上文的\(Split\)函数的参数只是为了简化,方便理解,实际中当然要带上一些其他跟\(y\)有关的参量。
线段树分裂每次只会往一个子树里递归,时间复杂度\(O(logn)\)。
-
分裂&合并时间复杂度分析
这个人是不是有健忘症啊,刚刚不是都讲过了吗。
我们现在讨论既有分裂又有合并时时间复杂度是如何的。首先我们默认初始节点非常少(否则一个合并的复杂度就已经不对了),假设两棵树在合并前有\(x\)个位置都非空,那么我们发现合并的过程其实就是删除\(x\)个点,复杂度就是\(O(x)\)。然而一次线段树分裂显然只能增加\(O(logn)\)个点,也就是说合并的时候最多删掉你之前裂开的点加上初始节点个点(这是废话,因为显然你删除的个数不可能大于增加的个数,这里初始节点较少,可以忽略)。所以,我们可以得出合并的总复杂度的上界就是分裂的复杂度,均摊后合并的复杂度也为\(O(logn)\)。
-
例题
李超线段树
-
概述
李超线段树的作用就是在一个二维平面内,给你若干条直线(线段),询问在某点时函数值最大(小)的线段是谁(或函数值),支持动态插入线段。
-
插入
以在 $[l,r] $插入一条直线 \(L\) 为例(\(mid\) 为中点):
- 若当前区间没插过线段,直接插进去。
- 若一条线段直接碾压另一条(在整个区间都更优),则直接替换然后退出。
- 否则 \(L\) 与这条直线一定有交点,我们把在 \(mid\) 处更优的那条线段留下,剩下的线段递归进子区间。
(线段指直线在当前区间的部分)
关于对3.的解释:注意到在 \(mid\) 处取值更优的线段,一定在该区间至少一半的地方取值更优,所以每次递归下去的那条线段长度不会超过原来的一半(递归到 \([l,mid]\) 或 \([mid+1,r]\))。正是这一点保证了复杂度(\(O(logn)\))。
以上是插入直线的复杂度,如果是插入线段,则你要先把它对应的拆成线段树上的一些区间,然后向上述那样插进去,复杂度 \(O(log^2n)\)。
-
单点查询 \(x\)
我们直接访问所有包含这个点的线段树上的点,并对这些点上记录的线段在 \(x\) 处的取值取个 \(\min\)(或 \(\max\)) 就好了。正确性比较好证明,因为我们在下放的过程中没有放过任何对答案可能有贡献的线段,也就是说最优解一定在树里。那么我们查询的时候访问了所以可能最优的地方,所以得出的结果也一定是最优的。
-
区间查询
这时候我们就要用标记永久化的思想。把记录当前区间线段的标记视为 \(lazy\_tag\),再记录一个标记表示当前最优解是多少。和普通标记永久化一样,我们在修改操作时顺便更新包含【你要修改的区间】的区间的最优解标记,在查询是一路上更新答案就行。唯一不同的就是 \(push\_up\) 的时候你不能只比较两个子区间的,还要把自己的加进来一起取个 \(\min\)(或 \(\max\))。正确性证明同上。
-
例题: