动态开点线段树
今天学校讲了这玩意。算是正式步入省选了。
觉得还不是太熟悉,有必要写一写这篇 blog。
动态开点线段树
Why to 动态开点?
下标太大、节点太多,炸空间。
离散化又需要离线,太麻烦。
于是我们动态地开点,不需要开的点就不开,减少空间开销。
这是开一个点的代码:
1 | int add(int l,int r,...) { |
基本操作
pushup
按照普通线段树写法模仿,把 p<<1
与 p<<1|1
换成 t[p].ls
和 t[p].rs
即可。
pushdown
同理。
插入下标为 的区间?这里以最大值为例。
1 | void modify(int &p,int l,int r,int L,int R,int x) { |
查询?这里也以最大值为例。
1 | int query(int &p,int l,int r,int L,int R) { |
反正和线段树很像。就是每次到一个点就把它新建一下就行了。
注意有初值的话最大值得用 ST 表处理一波初值。
例题
CF915E Physical Education Lessons
珂朵莉树好题
动态开点。因为 太大了,但 很小,所以需要开的点很少。
动态开点线段树维护区间和、区间赋值即可。
P5459 [BJOI2016]回转寿司
由于这题 可以为负,于是不能用双指针扫。
考虑使用前缀和。则本题就是要统计满足 的区间 的个数。
考虑固定 ,求有多少种 。
此时的 需要满足 。
可以开一个动态开点的权值线段树。从后往前扫一遍,每次加入它自己的 之后问线段树中在 范围内的点的个数。
也可以离散化后用权值树状数组搞
CF817F MEX Queries
珂朵莉树好题
动态开点线段树维护区间推平成 或 、区间取反和区间和。
询问就在线段树上二分,看哪里的区间和小于区间中数的个数就往哪里跑。
CF803G Periodic RMQ Problem
好题。
由于 太大了,显然要动态开点。
但是在新建一个节点的时候,我们需要将它的初值赋好。
用一个 ST 表维护一下原序列的区间 用来给开的点赋初值,然后就是区间推平、区间 的动态开点线段树板子了。
特别地,如果开的点经过两个 的段(因为题目中 循环了 次),那么分两段讨论即可。如果开的点跨越了整个段,那么直接用整个序列的 来赋初值。