「末世追忆」:“勇而无畏之人,在于面对不可掘凿的基土之时,开辟直达深渊的幽径。”
动态开点线段树
这种东西其实在很久以前就见识过了,不过那是一种动态开点线段树的变种,名为主席树,也就是可持久化线段树。但实际上,动态开点也可以用在普通线段树上。
首先,我们知道,一棵完全线段树的结点个数为
再次重复,因为是动态开点,所以父子关系不再满足二倍,所以直接存储子结点编号。
示例写法
参考代码
1 | const int MAXN=1e5+10; |
然后你就会发现在这种近乎开满的情况下,动态开点和完全线段树在时间,空间,码量上都差别不大,甚至还要难写一点。但是,如果是对于一些比较特殊的区间问题,动态开点将会派上大用场。
线段树合并
顾名思义,把两棵线段树合并在一起,和
结果会发现,如果是完全线段树的话,我们递归合并不如直接扫一遍线段树数组来合并。所以说,线段树合并在完全线段树的运用下就是毫无作用的,这也是为什么在这篇文章的前面我们会介绍动态开点线段树,因为线段树合并一般都用在动态开点上(且用于值域类线段树),时间复杂度在
离线破结式合并
假设现在有两棵动态开点线段树,记为
参考代码
1 | int merge(int p1,int p2,int l,int r) |
在线动态开点式合并
顾名思义,对于合并
参考代码
1 | int merge(int p1,int p2,int l,int r) |
例题
P3605 [USACO17JAN]Promotion Counting P
其实我觉得这道题更适合练手一些。毕竟不涉及其它操作,而且线段树存储的参数也只有一个。
需要注意的是,动态开点线段树一般会搭配离散化来使用,而且其空间一般而言都需要到 MAXN<<4
或者 MAXN<<5
就差不多。否则会
AC Code
1 | const int MAXN=1e5+10; |
CF600E Lomsat gelral
对于每一个结点建立一棵动态开点权值线段树,然后维护当前结点(肯定只有一种颜色)的答案和,然后从叶结点向上合并并更新答案即可。
AC Code
1 | const int MAXN=1e5+10,MAXTREE=3e6+10; |
雨天的尾巴
名副其实的线段树合并模板题。
单独写了一篇博客
P3521 [POI2011]ROT-Tree Rotations
有些思考难度,至少我没有做出来。
因为读入是按照递归读入的,所以也考虑在读入的时候处理子树,并最后递归得到答案。考虑当前子树对整棵树的影响,记录
AC Code
1 | const int MAXN=1e6+10; |
线段树分裂
顾名思义,当维护整棵线段树十分困难的时候,考虑拆分成多棵线段树分别维护,形式上线段树合并的逆操作。
可以联想
子树大小分裂 / 排名分裂
一般而言,线段树分裂使用的范围也是动态开点值域线段树,所以呢,分裂形式与
首先说按排名分裂,即分裂出第
参考代码
1 | void split(int p1,int &p2,int k) |
这个思路类似于查找区间第
例题
P5494 【模板】线段树分裂
一个一个分析操作,第一个操作,就是按权值分裂两次并合并第一三个,对于如何把排名分裂转换为权值分裂,我们可以用到操作
注意:不!要!把!下!标!打!错!了!
AC Code
1 | const int MAXN=1e6+10; |
P2824 [HEOI2016/TJOI2016]排序
这道题有一个简化版