动态开点线段树

引入

众所周知,普通的线段树,我们一般开 4 n {4n} 4n 的数组以避免越界。然而,当一些题目 n {n} n 很大的时候,如果正常的去建一颗线段树,开 4 n {4n} 4n 倍的空间,会超出空间限制。

普通的线段树是开一个满二叉树来建树的,这也就意味着很多情况下线段树的节点并没有被完全使用,造成了极大的空间浪费。
  
这时候,就需要动态开点线段树


动态开点线段树

普通的线段树左右儿子表示方法:

左儿子:u<<1

右儿子:u<<1|1

这样我们可以通过父节点u直接计算出左右儿子,但是这极大的浪费了空间。所以我们考虑先不建树,等到用到没有开的节点时,再将这个节点加入树中。这样的话我们就可以减少空间开销,从而优化空间复杂度。

但因为是动态开点,所以节点本身是无序的,其排列顺序仅与遍历顺序有关。在这里,我们采用链式存储法,即对一个节点建立左右儿子指针。这样,建立新的节点时,就不会浪费多余的空间,我们只需要开 2 n {2n} 2n 的数组即可,也就是我们一个满二叉树的节点的个数。


作用

一般作用:

  1. 节约空间
  2. 用于主席树中

实现代码

struct

因为递归方式是固定的,所以动态开点线段树的结构体不需要加上区间限制。

struct node {
	int l, r; // 指向左右儿子
	int add, sum;
} tr[N << 1];

build

  • if(!p) p = ++idx: 即为动态开点核心代码
void build(int &p, int l, int r) {
	if(!p) p = ++idx;
	if(l == r) { tr[p].sum = a[l]; return ;}
	int mid = l + r >> 1;
	build(tr[p].l, l, mid), build(tr[p].r, mid + 1, r);
	pushup(p);
}

modify

  • l , r {l,r} l,r:是当前节点 p {p} p 所维护的区间。
  • q l , q r {ql, qr} ql,qr:是询问的区间。
void modify(int &p, int l, int r, int ql, int qr, int k) {
	if(!p) p = ++idx;
	if(l >= ql && r <= qr) {
		tr[p].sum += (r - l + 1) * k;
		tr[p].add += k;
		return ;
	}
	pushdown(p, l, r);
	int mid = l + r >> 1;
	if(ql <= mid) modify(tr[p].l, l, mid, ql, qr, k);
	if(qr > mid) modify(tr[p].r, mid + 1, r, ql, qr, k);
	pushup(p);
}

query

int query(int p, int l, int r, int ql, int qr) {
	if(l >= ql && r <= qr) { return tr[p].sum; }
	int mid = l + r >> 1;
	pushdown(p, l, r);
	int v = 0;
	if(ql <= mid) v = query(tr[p].l, l, mid, ql, qr);
	if(qr > mid) v += query(tr[p].r, mid + 1, r, ql, qr);
	return v;
}

pushup

void pushup(int p) {
	tr[p].sum = tr[tr[p].l].sum + tr[tr[p].r].sum;
}

pushdown

void pushdown(int p, int l, int r) {
	if(tr[p].add) {
		int mid = l + r >> 1;
		tr[tr[p].l].sum += (mid - l + 1) * tr[p].add, tr[tr[p].l].add += tr[p].add;
		tr[tr[p].r].sum += (r - mid) * tr[p].add, tr[tr[p].r].add += tr[p].add;
		tr[p].add = 0;
	}
}

完整代码:

  • 直接建全树
#include <bits/stdc++.h>
using namespace std;

const int N = 100010;
#define int long long

struct node {
	int l, r;
	int add, sum;
} tr[N << 1];
// 正常线段树,这里不开4倍大小会RE

int n, m, idx, root;
int a[N];

void pushup(int p) {
	tr[p].sum = tr[tr[p].l].sum + tr[tr[p].r].sum;
}

void pushdown(int p, int l, int r) {
	if(tr[p].add) {
		int mid = l + r >> 1;
		tr[tr[p].l].sum += (mid - l + 1) * tr[p].add, tr[tr[p].l].add += tr[p].add;
		tr[tr[p].r].sum += (r - mid) * tr[p].add, tr[tr[p].r].add += tr[p].add;
		tr[p].add = 0;
	}
}

void build(int &p, int l, int r) {
	if(!p) p = ++idx;
	if(l == r) { tr[p].sum = a[l]; return ;}
	int mid = l + r >> 1;
	build(tr[p].l, l, mid), build(tr[p].r, mid + 1, r);
	pushup(p);
}

void modify(int &p, int l, int r, int ql, int qr, int k) {
	if(!p) p = ++idx;
	if(l >= ql && r <= qr) {
		tr[p].sum += (r - l + 1) * k;
		tr[p].add += k;
		return ;
	}
	pushdown(p, l, r);
	int mid = l + r >> 1;
	if(ql <= mid) modify(tr[p].l, l, mid, ql, qr, k);
	if(qr > mid) modify(tr[p].r, mid + 1, r, ql, qr, k);
	pushup(p);
}

int query(int p, int l, int r, int ql, int qr) {
	if(l >= ql && r <= qr) { return tr[p].sum; }
	int mid = l + r >> 1;
	pushdown(p, l, r);
	int v = 0;
	if(ql <= mid) v = query(tr[p].l, l, mid, ql, qr);
	if(qr > mid) v += query(tr[p].r, mid + 1, r, ql, qr);
	return v;
}

signed main() {
	cin >> n >> m;
	for (int i = 1; i <= n; i++) cin >> a[i];
	
	build(root, 1, n);
	
	int op, x, y, k;
	while(m--) {
		cin >> op >> x >> y;
		if(op == 1) {
			cin >> k;
			modify(root, 1, n, x, y, k);
		} else {
			cout << query(root, 1, n, x, y) << endl;
		}
	}
	return 0;
}
  • 也可以动态插入节点
#include <bits/stdc++.h>
using namespace std;

const int N = 100010;
#define int long long

struct node {
	int l, r;
	int add, sum;
} tr[N << 1];

int n, m, idx, root;

void pushup(int p) {
	tr[p].sum = tr[tr[p].l].sum + tr[tr[p].r].sum;
}

void pushdown(int p, int l, int r) {
	if(tr[p].add) {
		int mid = l + r >> 1;
		tr[tr[p].l].sum += (mid - l + 1) * tr[p].add, tr[tr[p].l].add += tr[p].add;
		tr[tr[p].r].sum += (r - mid) * tr[p].add, tr[tr[p].r].add += tr[p].add;
		tr[p].add = 0;
	}
}

void modify(int &p, int l, int r, int ql, int qr, int k) {
	if(!p) p = ++idx;
	if(l >= ql && r <= qr) {
		tr[p].sum += (r - l + 1) * k;
		tr[p].add += k;
		return ;
	}
	pushdown(p, l, r);
	int mid = l + r >> 1;
	if(ql <= mid) modify(tr[p].l, l, mid, ql, qr, k);
	if(qr > mid) modify(tr[p].r, mid + 1, r, ql, qr, k);
	pushup(p);
}

int query(int p, int l, int r, int ql, int qr) {
	if(l >= ql && r <= qr) { return tr[p].sum; }
	int mid = l + r >> 1;
	pushdown(p, l, r);
	int v = 0;
	if(ql <= mid) v = query(tr[p].l, l, mid, ql, qr);
	if(qr > mid) v += query(tr[p].r, mid + 1, r, ql, qr);
	return v;
}

signed main() {
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		int x; cin >> x;
		modify(root, 1, n, i, i, x);
	}
	
	int op, x, y, k;
	while(m--) {
		cin >> op >> x >> y;
		if(op == 1) {
			cin >> k;
			modify(root, 1, n, x, y, k);
		} else {
			cout << query(root, 1, n, x, y) << endl;
		}
	}
	return 0;
}
  • 5
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ღCauchyོꦿ࿐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值