线段树

普通线段树

普通的线段树先不谈,我们来看一点比较高级的科技。

  • 直接给一些好题:

  1. luogu CF446C DZY Loves Fibonacci Numbers 题解

线段树分治

啊啊啊今天突然发现全机房只有我不会线段树分治。。。

有时我们会在时间轴上进行一些操作,或者清除我们之前的操作。这时候我们可以使用这样一种思路:直接在时间轴上建立线段树,然后以类似标记永久化的形式把所有操作(因为有清除的存在,所以每个操作都会有他生效的一个区间)放进线段树里(对应区间),最后对这棵线段树进行\(DFS\),进入一个点的时候就把影响统计进来,回溯的时候去掉影响。

显然每个询问都是在叶子节点上的,我们只要在访问到叶子的时候统计一下就行了。

  • 例题

  1. luogu P5787 二分图 /【模板】线段树分治 题解

线段树合并/分裂

一般用于权值线段树(至少我见过的)。

  • 合并

考虑合并两棵线段树上的对应节点,有两种情况:

  1. 有一棵树是空的,直接返回另一棵非空树
  2. 两棵树都非空,合并这两个节点,并递归左右子树

稍微思考可以发现,线段树合并并不能保证复杂度,因为显然合并两棵满线段树的时间复杂度是\(O(n)\)。所以线段树合并一般使用在动态开点并且初始节点很少的题目中。使用前一定要注意复杂度分析。

  • 分裂

要求你从权值线段树中以第\(k\)小的点为界,前面的属于原树,后面的把分离出来成为一棵新树。这样把一棵线段树分裂成两棵的过程,叫做线段树分裂。

这里我们再多记一个\(siz[x]\)表示节点\(x\)内有多少数。现在我们要以第\(k\)个数为界分裂\(x\)节点,设为\(Split(x,k)\),继续考虑分类讨论(下面\(l(x),r(x)\)分别表示\(x\)的左、右节点,并设分离出来的新树为\(y\)):

  1. \(siz[l(x)]<k\),则左子树依然还是\(x\)的,递归右子树\(Split(r(x),k-siz[l(x)])\)
  2. \(siz[l(x)]==k\),则正好左边是\(x\)的,右边都归\(y\),结束
  3. \(siz[l(x)]>k\),则右子树全部归\(y\),递归左子树\(Split(l(x),k)\)

注意上文的\(Split\)函数的参数只是为了简化,方便理解,实际中当然要带上一些其他跟\(y\)有关的参量。

线段树分裂每次只会往一个子树里递归,时间复杂度\(O(logn)\)

  • 分裂&合并时间复杂度分析

这个人是不是有健忘症啊,刚刚不是都讲过了吗。

我们现在讨论既有分裂又有合并时时间复杂度是如何的。首先我们默认初始节点非常少(否则一个合并的复杂度就已经不对了),假设两棵树在合并前有\(x\)个位置都非空,那么我们发现合并的过程其实就是删除\(x\)个点,复杂度就是\(O(x)\)。然而一次线段树分裂显然只能增加\(O(logn)\)个点,也就是说合并的时候最多删掉你之前裂开的点加上初始节点个点(这是废话,因为显然你删除的个数不可能大于增加的个数,这里初始节点较少,可以忽略)。所以,我们可以得出合并的总复杂度的上界就是分裂的复杂度,均摊后合并的复杂度也为\(O(logn)\)

  • 例题

  1. luogu P5494 【模板】线段树分裂 题解

李超线段树

  • 概述

李超线段树的作用就是在一个二维平面内,给你若干条直线(线段),询问在某点时函数值最大(小)的线段是谁(或函数值),支持动态插入线段。

  • 插入

以在 $[l,r] $插入一条直线 \(L\) 为例(\(mid\) 为中点):

  1. 若当前区间没插过线段,直接插进去。
  2. 若一条线段直接碾压另一条(在整个区间都更优),则直接替换然后退出。
  3. 否则 \(L\) 与这条直线一定有交点,我们把在 \(mid\) 处更优的那条线段留下,剩下的线段递归进子区间。

(线段指直线在当前区间的部分)

关于对3.的解释:注意到在 \(mid\) 处取值更优的线段,一定在该区间至少一半的地方取值更优,所以每次递归下去的那条线段长度不会超过原来的一半(递归到 \([l,mid]\)\([mid+1,r]\))。正是这一点保证了复杂度(\(O(logn)\))。

以上是插入直线的复杂度,如果是插入线段,则你要先把它对应的拆成线段树上的一些区间,然后向上述那样插进去,复杂度 \(O(log^2n)\)

  • 单点查询 \(x\)

我们直接访问所有包含这个点的线段树上的点,并对这些点上记录的线段在 \(x\) 处的取值取个 \(\min\)(或 \(\max\)) 就好了。正确性比较好证明,因为我们在下放的过程中没有放过任何对答案可能有贡献的线段,也就是说最优解一定在树里。那么我们查询的时候访问了所以可能最优的地方,所以得出的结果也一定是最优的。

  • 区间查询

这时候我们就要用标记永久化的思想。把记录当前区间线段的标记视为 \(lazy\_tag\),再记录一个标记表示当前最优解是多少。和普通标记永久化一样,我们在修改操作时顺便更新包含【你要修改的区间】的区间的最优解标记,在查询是一路上更新答案就行。唯一不同的就是 \(push\_up\) 的时候你不能只比较两个子区间的,还要把自己的加进来一起取个 \(\min\)(或 \(\max\))。正确性证明同上。

  • 例题:

  1. luogu P4097 [HEOI2013]Segment(模板题) 题解
  2. luogu P4069 [SDOI2016]游戏 题解

posted @ 2020-06-10 02:07  With_penguin  阅读(111)  评论(0编辑  收藏  举报