动态开点线段树
为了准备
我们将从以下几个方面去讲解:
目录什么是动态开点线段树
动态开点线段树是在空间不够时动态的create
线段树上的结点,与普通线段树直接build
不同,动态开点线段树在使用的过程中动态create
。
优缺点使用情况
优点:
- 元素的值域很大,比如
1e9
且强制在线(无法离散化)。
缺点:
- 比正常的线段树复杂一些。
思路讲解及实现
依据我人脑库中的继承,我的线段树一般有几个基本方法需要实现:
push
,进行操作前往子结点传递信息。pull
,进行操作后从子结点更新父节点。update(start, end, rt)
,进行区间更新query(start, end, rt)
, 进行区间查询build
,但是这里不需要了。
下面解释下tree node
:
ls
:left son
rs
:right son
val
: 当前node
对应的价值lazy
:各种懒标记(add
,mul
)等。
只需要注意的是,动态开点的具体流程,假如ls
或者rs
为0,则代表对应的节点还没有创建,需要我们动态创建。
思考一下,动态开点应该放在哪个方法里面?
Answer:
push
方法中,因为我们每次操作前都会调用push
往子结点传递信息,如果子结点不存在我们再动态创建。
定义结构体Node
:
struct Node{
int ls, rs; // lson, rson
int val, add; // val -> value, add -> lazy add
Node (int a = 0, int b = 0, int c = 0, int d = 0){
ls = a;
rs = b;
val = c;
add = d;
}
} tr[MAXN];
定义push
,注意我们的宏,主要是为了写的方便,其中len
代表区间的长度,因为我们的结构体中没有存区间长度所以我们要传进来。
if (!ls(p)) ls(p) = ++ cnt;
if (!rs(p)) rs(p) = ++ cnt;
这两行代码就是用于动态的create
的。
除此之外还需要注意,如果我们是按这种规则区分的:
- 左边 \([start, mid] | mid = \frac{start + mid}{2}\)
- 右边 \([mid+1,end] | mid = \frac{start + mid}{2}\)
这样,我们列出公式:
\[\begin{array}{ll} Len_l &=\dfrac{end + start}{2} - start + 1\\ Len_l &=\dfrac{end - start + 2}{2} \\ Len_l &=\dfrac{len + 1}{2} = \verb|len-(len/2)|\\ Len_r &=\dfrac{len - 1}{2} = \verb|len/2| \end{array} \]于我们的常识有点不符,左边的区间长度为\(Len_l = \verb|len-(len/2)|\)。
其他的都和正常的区间线段树类似。
#define ls(x) tr[x].ls
#define rs(x) tr[x].rs
#define val(x) tr[x].val
#define add(x) tr[x].add
void push(int p, int len){
// check exists
if (!ls(p)) ls(p) = ++ cnt;
if (!rs(p)) rs(p) = ++ cnt;
if (add(p) == 0) return void();
if (add(p) == -1){
val(ls(p)) = val(rs(p)) = 0;
}else if (add(p) == 1){
val(ls(p)) = len - (len / 2);
val(rs(p)) = len / 2;
}
add(ls(p)) = add(p);
add(rs(p)) = add(p);
add(p) = 0;
}
Example
LeetCode Range Module
代码:https://leetcode.cn/submissions/detail/350662555/
需要注意,如果是这种OJ,需要memset
结构体或者写到类内。