(三)数据结构
线段树
普通线段树
我的线段树 \(debug\)(记录一下做题时犯的错误):
- 检查 \(build\) 函数是否调用。\(\to segmentation~fault\)
- 区间操作 \(ql\) 是否有可能大于 \(qr\to segmentation~fault\)。
- 结构体中元素初值问题(是否赋初值,懒标记初值是否与题目操作冲突)\(\to\) 输出可能变得极大
- 动态开点线段树记得建第一个点(坑死我啦 \(\to\) 什么都有可能 \(TLE,RE,segmentation~fault\)
- 线段树空间记得开 \(4\) 倍(经典错误)\(\to RE\)
- 检查各个部分的手误
- 检查数据范围,数组是否开小 \(\to RE\)
多刷了几道题,学习到了一些维护数据的方法。
例题 \(1.1\) :区间方差 - 洛谷
这道题思路比较简单,就是将所求式子化简开来,转变成维护区间和与区间平方和即可。
例题 \(1.2\):[TJOI2018]数学计算 - 洛谷
由于模数不一定为质数,所以逆元并不一定存在,故不能直接计算。
基于操作建立线段树维护乘积,根节点保存答案。
例题 \(1.3\):[SCOI2007] 降雨量 - 洛谷
离散化后,分讨 \(x,y\) 的存在性即可,需要考虑的情况有点小多。
例题 \(1.4\):[HEOI2016/TJOI2016]排序 - 洛谷
二分答案,考虑 \(check(mid)\) 函数的写法:
观察每次排序,它会把小于 \(mid\) 的数放到一边,大于 \(mid\) 的数放到另一边,于是上线段树区间修改。
考虑将小于等于 \(mid\) 的数记为 \(0\) ,大于 \(mid\) 的数记为 \(1\),
最后模拟完所有操作后,考察第 \(q\) 位是否为 \(0\) 即可。
二分的正确性分析:若当前理想解不成立,那么比它更小的解也不可能成立,二分正确。
例题 \(1.5\):小白逛公园 - 洛谷
区间最大子段和模板,维护区间 \(lmax, rmax, sum, imax\) 转移即可。
线段树优化建图
对于如一个点向区间连边的问题,由于边数过多,建图的复杂度直接爆炸,此时可以考虑线段树优化建图。
概述:
假设一个点 \(x\) 要向一个区间 \([l,r]\) 连一条边权为 \(w\) 的边,这个区间必然可以用线段树表示成不超过 \(\log n\) 的小区间。
于是,将这个点向这些小区间的代表节点(虚拟节点)连边。那么就可以使所连的边数大大减少。
此外,从这些小区间到它们的子区间应连一条边权为 \(0\) 的边,可以这样想象,能连到这个区间,必然也能连到它的子区间中的点。
例题 \(1.6\):Legacy - 洛谷
对于这道题目,它既要求由点向区间连边,也要求区间向点连边。
考虑建立两棵线段树,分别维护入边和出边,第二棵线段树需要将父区间连向子区间的边反向。
对于操作 \(1\) ,由线段树 \(2\) 的叶子结点向线段树 \(1\) 连边,操作 \(2\) 反之。
然后就是最短路模板了。
线段树的标记永久化
对于不支持懒标记下传的线段树而言,需要用到标记永久化的技巧。
标记永久化的前置条件:修改顺序不影响最后答案。
如果能正常 \(pushdown\) 且满足上述条件,那么就可以进行标记永久化。
思路简述:
懒标记应该 \(pushdown\) 时,按住不下放,在最后询问的时候考虑从根节点到询问区间的所有懒标记即可。
这样说可能不明晰,看个例子:
例 \(2.1\):【模板】线段树 1 - 洛谷
权值线段树
顾名思义,权值线段树是维护与值域相关信息的线段树。
举个例子,权值线段树的叶子维护有几个 \(1,2\) ,它们的父亲节点维护有几个 \(1\) 和 \(2\) 。
可以武断地说,权值线段树维护的是值域上各个数的个数。
应用:权值线段树最广泛的应用就是解决整个数列的第 \(k\) 大(小)的数,具体方法如下(以求第 \(k\) 小数为例):
权值线段树的父亲节点维护数的个数和,在查询时:如果左子区间的数的个数大于 \(k\) 个,那么进入左子区间,否则进入右子区间。
思路比较简单,多配合其它知识一起考察,如线段树合并,可持久化线段树等。
例题:见例题 \(4.2\) [HNOI2012]永无乡 - 洛谷。
线段树合并
前置知识:动态开点线段树
线段树合并常应用于树上问题,用于合并两棵线段树之间的信息。
前置条件:能够快速合并两个叶子结点。
合并过程:
合并分为两类:
-
合并叶子结点的信息(单点合并)
-
合并左右儿子的信息(区间合并)
如果当前两个节点有一个为空,\(return\)(空节点合并后信息不改变)。
否则新建一个节点,保存合并后线段树的信息。
继续递归,直到叶子结点或有一个为空,回溯时返回新建节点编号。
容易发现,合并的复杂度为两棵线段树重合的节点个数。
技巧:设被合并的线段树为 \(seg_x\) 和 \(seg_y\) ,如果被合并的线段树 \(seg_y\) 的信息以后不会再用到,那么可以不用新建节点,而将信息直接合并到 \(seg_x\) 的节点上。
例题 \(4.1\):[Vani有约会]雨天的尾巴 /【模板】线段树合并 - 洛谷
对于每个节点建立一棵线段树,配合树上差分修改,最后向上合并线段树即可。
例题 \(4.2\):[HNOI2012]永无乡 - 洛谷
裸的线段树合并。
一个套路:并查集维护连通性,每次操作就只用在并查集的代表元素之间。
这样对于每个点开一棵权值线段树,对于每个操作合并代表元素即可。
例 \(4.3\):[NOIP2016 提高组] 天天爱跑步
将每次修改分两段处理,查询时查询 \(dep+w\) 的值即可。