动态开点线段树学习笔记

动态开点线段树学习笔记

前言

这篇博客只是为了后面主席树做铺垫的,而且动态开点线段树非常的简单,所以就讲的比较少。

正文

我们都知道,普通的线段树的空间是4倍,因为会有很多不需要的节点,那么动态开点线段树就被发明出来了。
所谓动态开点线段树,就是每一次如果我们需要一个新的节点,那么我们就给他开一个新的节点,这样我们就不需要一下子开非常大的个数,这种思想依旧使用与主席树。简单讲一下主席树,主席树的原理就是我们需要修改的历史版本,那么我们就复制一份我们需要查询的链,在这个链上修改,在将这个链重新编号,待会原来的线段树中,这个时候我们需要重新开点,那么这样可以让主席树每一次的空间复杂度变成\(log(n)\)而不是\(n\)。那么回到我们的动态开点线段树,这种线段树只需要开\(2n\)就可以了,也就是我们一个满二叉树的节点的个数。

步骤

首先定义结构体:

struct node {
    int lc, rc, s, lazy;//lc,rc表示的是左节点的编号和右节点的编号,因为我们无法直接像普通线段树一样的O(1)求解,那么就要记录儿子编号,s就是维护的权值,lazy是懒标记
}

那么其他的操作都是和普通线段树一模一样的
举一个例子,例如说是单点修改

void update_point(int nod, int l, int r, int k, int v) {//把k变成v
    if (l == r) { 
        tr[nod].s = v;
        return;
    }
    if (tr[nod].lazy) pushdown(nod);
    int mid = (l + r) >> 1;
    if (k <= mid) update_point(tr[nod].lc, l, mid, k, v);
    else update_point(,tr[nod].rc, mid + 1, r, k, v);
    pushup(nod);
}

我们会发现只有在递归调用时左右儿子的调用是不一样的,其他和普通线段树一模一样。
但是在建树的过程和一般的线段树有一点点的不一样:

void build(int &nod, int l, int r) {//取地址实现修改操作
    nod = ++ tot;//开节点
    if (l == r) {
        tr[nod].s = a[l];
        return;
    }
    int mid = (l + r) >> 1;
    build(tr[nod].lc, l, mid);
    build(tr[nod].rc, mid + 1, r);
    pushup(nod);
}
posted @ 2019-03-18 08:50 chhokmah 阅读(...) 评论(...) 编辑 收藏