动态开点线段树学习笔记
前言
这篇博客只是为了后面主席树做铺垫的,而且动态开点线段树非常的简单,所以就讲的比较少。
正文
我们都知道,普通的线段树的空间是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);
}