动态开点线段树

动态开点线段树

为了准备google的面试刷题的时候发现这个知识点其实我一直不太熟悉,所以专门写一篇blog准备一下。

我们将从以下几个方面去讲解:

什么是动态开点线段树

动态开点线段树是在空间不够时动态的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 + end}{2}\)
  • 右边 \([mid+1,end] | mid = \frac{start + end}{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)|\)

其他的都和正常的区间线段树类似。注意,这里我们不能使用\(Len_r = \dfrac{len-1}{2}\),这是因为,虽然左边我们可以直接用\(\dfrac{len+1}{2}\),但是这其实有取floor的感觉,不能直接用加减考虑。

#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;
}

你会发现我们的结构体中并没有存储节点所代表的 leftright ?这是值得思考的问题,这是因为,我们默认 root 节点代表的是 [MIN, MAX],这样我们就可以在 query update 等函数的参数中维护当前节点 p 代表的区间。

TODO

Example

LeetCode Range Module

代码:https://leetcode.cn/submissions/detail/350662555/

需要注意,如果是这种OJ,需要memset结构体或者写到类内。

posted @ 2022-08-15 22:03  Last_Whisper  阅读(694)  评论(0编辑  收藏  举报