动态开点线段树(粗略浅析)

动态开点线段树就是把原来一开始需要 \(build\) 的建立线段树省去,改为对于每一次修改操作时,每次根据区间修改的需要在线段树上开出至多 \(\log n\) 个节点的算法。
相信普通的线段树,学了就一定能写(bei)出来

如果把 \(n\) 的值域开到 \(10^9\),而询问次数 \(m\) 并不大,若用普通线段树一开始就要开出 \(4 \times 10^9\) 的数组,肯定不能接受。

这时,我们便考虑到了动态开点。

在每次修改操作时,如果发现马上要搜到的点没有开过,那我们就要开出新结点,并更新它父亲的左/右儿子结点。

所以这就要求我们在递归时的 \(k\) 需要取值符,这样回溯时得以更新父亲的结点。

动态开点与普通有一个较大的不同:动态开点并不是一棵完全二叉树,需要记录下左右儿子节点的编号,而普通的可以通过位运算直接得出。

对于 pushdown 操作,我们思考该操作是向下传递信息,但下面可能还未开出节点,这样做不仅浪费时间,还浪费空间。

所以我们可以不在 modify 时下传标记,而是等到需要查询下面的区间,不得不传时我们再传即可。

在下传时也应当注意:传递时若发现左右儿子点没开出来,要先开出点来再下传标记。

// by youyou2007 in 2022.
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <queue>
#include <stack>
#include <map>
#define REP(i, x, y) for(int i = x; i < y; i++)
#define rep(i, x, y) for(int i = x; i <= y; i++)
#define PER(i, x, y) for(int i = x; i > y; i--)
#define per(i, x, y) for(int i = x; i >= y; i--)
#define lc (k << 1)
#define rc (k << 1 | 1)
using namespace std;
/*
inline int read()
{
    int s = 0, w = 1; char ch = getchar();
    while(ch < '0' || ch > '9'){ if(ch == '-') w = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
    return s * w;
}
*/
const int LOG = 19;
const int N = 5e5 + 5;
int n, m;
struct node{
	int l, r;
	long long sum;
}f[N * LOG];
long long ltag[N * LOG];
int a[N], cnt, root = 0;
void pushup(int k)
{
	f[k].sum = f[f[k].l].sum + f[f[k].r].sum; 
}
void pushdown(int l, int r, int k)
{
	int mid = (l + r) / 2;
	if(!f[k].l) f[k].l = ++cnt;//注意在下传标记之前若没开点要开点
	if(!f[k].r) f[k].r = ++ cnt; 
	f[f[k].l].sum += (mid - l + 1) * ltag[k];
	ltag[f[k].l] += ltag[k];
	f[f[k].r].sum += (r - (mid + 1) + 1) * ltag[k];
	ltag[f[k].r] += ltag[k];
	ltag[k] = 0;
}
void modify(int l, int r, int ql, int qr, int &k, int d)//注意 k 之前要加取值符,因为还要回溯更新父亲节点的信息
{
	if(!k) k = ++cnt;//一样,没开点的先开点
	if(l >= ql && r <= qr)
	{
		f[k].sum += (r - l + 1) * d;
		ltag[k] += d;
		return;
	}
	/*if(ltag[k])//这里不用下传标记,节省时空
	{
		pushdown(l, r, k);
	}*/
	int mid = (l + r) / 2;
	if(qr <= mid)
	{
		modify(l, mid, ql, qr, f[k].l, d);
	}
	else if(ql >= mid + 1)
	{
		modify(mid + 1, r, ql, qr, f[k].r, d);
	}
	else
	{
		modify(l, mid, ql, mid, f[k].l, d);
		modify(mid + 1, r, mid + 1, qr, f[k].r, d);
	}
	pushup(k);
}
long long query(int l, int r, int ql, int qr, int k)
{
	if(k == 0) return 0;//如果发现这个点没开过直接返回 0
	if(l >= ql && r <= qr)
	{
		return f[k].sum;
	}
	if(ltag[k])//在此处下传标记即可。
	{
		pushdown(l, r, k);
	}	
	int mid = (l + r) / 2;
	if(qr <= mid)
	{
		return query(l, mid, ql, qr, f[k].l);
	}
	else if(ql >= mid + 1)
	{
		return query(mid + 1, r, ql, qr, f[k].r);
	}
	else
	{
		return query(l, mid, ql, mid, f[k].l) + query(mid + 1, r, mid + 1, qr, f[k].r);
	}
}
int main()
{
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; i++)
	{
		scanf("%d", &a[i]);
		modify(1, n, i, i, root, a[i]);
	}
	while(m--)
	{
		int opt, x, y, z;
		scanf("%d", &opt);
		if(opt == 1)
		{
			scanf("%d%d%d", &x, &y, &z);
			modify(1, n, x, y, root, z);
		}
		else
		{
			scanf("%d%d", &x, &y);
			printf("%lld\n", query(1, n, x, y, root));
		}
	}
	return 0;
}
posted @ 2022-08-04 23:00  panjx  阅读(252)  评论(1编辑  收藏  举报