标记永久化
下面是 OI-Wiki 中对标记永久化的介绍:
- 标记永久化:如果确定懒惰标记不会在中途被加到溢出(即超过了该类型数据所能表示的最大范围),那么就可以将标记永久化。标记永久化可以避免下传懒惰标记,只需在进行询问时把标记的影响加到答案当中,从而降低程序常数。具体如何处理与题目特性相关,需结合题目来写。这也是树套树和可持久化数据结构中会用到的一种技巧。
应用场合
当懒标记不会溢出,或者题目带有取模操作时,可以用标记永久化来卡常。
实现
- 省去 pushup:我们一路更新涉及到的每个区间的答案。
- 省去 pushdown:当不用继续向下深入时,我们给当前点打上懒标记,但后续不进行下传。查询时只需要算上这个点上懒标记的贡献就可以了。
最后以 P3372 【模板】线段树 1 - 洛谷 为例,示范一下标记永久化的用法。
本题的标记永久化由于涉及到区间长度,所以修查时目标区间 \((x, y)\) 都跟着分裂了。
注意 update
时只有 sum
算区间长度;query
时只有 tag
算区间长度。原理都是确定了影响范围才带系数。
进一步地讲,什么时候才确定了影响范围呢?下传的时候。
(其实 update
的全过程都可以看成是一次标记下传嘛)
#include<bits/stdc++.h>
#define F(i,l,r) for(int i(l);i<=(r);++i)
#define G(i,r,l) for(int i(r);i>=(l);--i)
using namespace std;
using ll = long long;
const int N = 1e5 + 5;
struct Segtree{
ll sum[N << 2], tag[N << 2];
void update(int u, int l, int r, int x, int y, int val){
sum[u] += val * (y - x + 1);
if(l >= x && r <= y){
tag[u] += val;
return ;
} int mid = (l + r) >> 1;
if(y <= mid) update(u * 2, l, mid, x, y, val);
else if(x > mid) update(u * 2 + 1, mid + 1, r, x, y, val);
else if(x <= mid && y > mid) update(u * 2, l, mid, x, mid, val), update(u * 2 + 1, mid + 1, r, mid + 1, y, val);
}
ll query(int u, int l, int r, int x, int y){
if(l >= x && r <= y) return sum[u];
int mid = (l + r) >> 1; ll ret = tag[u] * (y - x + 1);
if(y <= mid) return ret + query(u * 2, l, mid, x, y);
else if(x > mid) return ret + query(u * 2 + 1, mid + 1, r, x, y);
else if(x <= mid && y > mid) return ret + query(u * 2, l, mid, x, mid) + query(u * 2 + 1, mid + 1, r, mid + 1, y);
}
}tr;
int n, m;
signed main(){
cin >> n >> m; int x;
F(i, 1, n) cin >> x, tr.update(1, 1, n, i, i, x);
while(m --){
int op, x, y;
cin >> op >> x >> y;
if(op == 2){
cout << tr.query(1, 1, n, x, y) << '\n';
}
else{
int k;
cin >> k;
tr.update(1, 1, n, x, y, k);
}
}
return fflush(0), 0;
}
练习
C241108B. 简单题