模板——数据结构2
模板——数据结构2
线段树,树状数组,动态开点线段树,线段树合并,主席树,树套树,平衡树。
线段树
- 维护哪些信息?区间和。
- 维护哪些标记?区间加标记。
- 如何合并区间信息?直接相加。
- 如何快速修改区间信息?加上 增量乘区间长度。
- 如何合并标记?直接相加。
单点修改,区间查询
1 |
|
区间修改,区间查询
1 |
|
树状数组
树状数组每个节点管理的是一段区间。利用数的二进制分解,每个前缀区间 \([1,x]\) 都可以使用 \(O(\log n)\) 个小区间覆盖。查询:找到这些小区间并相加,即求出区间和。更新:更新所有包含这个位置 \(x\) 的小区间。
普通
1 | ll lb(ll x) { return x & (-x); } |
二维
1 | int lb(int x) { return x & -x; } |
动态开点线段树
普通
通常没有 build 操作。
1 | typedef long long ll; |
可持久化数组
一次单点修改,最多会让一个线段树上 \(\log n\) 个节点发生改变,相邻历史版本其余的点可以共用。这样一次更改的时间复杂度是 \(\log n\),所用空间也是 \(\log n\)。
新产生的节点中,只有一个儿子是新的,另一个儿子一定指向上一版本的儿子,从而实现共享。 虽然每个节点可能出现多个父亲,但线段树中,我们都是找儿子节点,不会出现错误。
1 |
|
线段树合并
线段树明明是好好的线段树我们为什么要合并它呢?
集合需要合并,线段树只是承载集合的工具。动态开点线段树,这种线段树很有可能不是满的,这时候我们直接合并可能就有些不妙了,那这里就是线段树合并这个算法出场的时候了。
对每个节点 \(u\),都求它子树的答案,然后有很多修改,不能对每个询问都暴力遍历子树,可以考虑离线询问后线段树合并、dsu on tree 等。
1 | ll merge(ll ra, ll rb, ll l, ll r) { |
主席树
静态区间第 \(k\) 小。对于原序列的每一个前缀 $ [1…i]$ 建立出一棵值域线段树维护各个数出现次数,则其树是可减的。主席树的每个节点保存的是一棵线段树,维护的区间信息,结构相同,具有可加减性(关键),利用可加减性同时行动。充分利用共同数据来减少时间和内存消耗。
1 |
|
树套树
现在给出 \(1∼n\) 的一个排列,按照某种顺序依次删除 \(m\) 个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数。
每个树状数组的节点都是一棵权值线段树,可以维护树状数组管辖的区间中,大小范围处于 \([L,R]\) 的数字个数。
1 |
|
平衡树
这里使用的是 FHQ-Treap。
split 实现集合操作
1 |
|
splitK 实现区间操作
这里实现的是区间翻转,需要类似于线段树的懒标记和 pushdown 操作。
1 |
|
这里实现的是动态数组(增、删、访问都是 \(O(\log n)\))。
1 | // 随机访问 a[i] |