动态开点线段树(粗略浅析)
动态开点线段树就是把原来一开始需要 \(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;
}