标记永久化

下面是 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. 简单题

posted @ 2024-11-10 15:50  superl61  阅读(4)  评论(0编辑  收藏  举报