Bootstrap

线段树模板 2 (区间修改)


区间修改和单点修改的区别

还是上一章的涉及修改的区间和查询
如果把单点修改的算法延续到区间修改
即 [x, y] 看作是 (y - x + 1) 次单点修改,复杂度甚至会超越暴力求解
这时候就要引入 lazy 标记 ,这是单点修改没有的


lazy标记是什么?

举一个例子:
班级 4 个人交了 100 元班费
财务只需要在账面上记录 [1, 4] + 100 即可
4 个人买教材花了 50 元
财务只需要把标记改成 [1, 4] +50 即可
把原本对 4 个人要分别进行的操作改称对这个区间加一个标记

现在 [1, 2] 号两个同学要补办校服花费 30 元
这笔钱不能让剩下两个同学帮他们承担
所以作为财务,你选择把对 [1, 4] 的标记向下分别传递给 [1, 2] 和 [3, 4]
此时 [1, 4] 上的标记改成 0,[1, 2] 和 [3, 4] 都加上 50
此时我们再把 [1, 2] 的标记减去 30 代表他们两个人补校服的花费
同理后续需要对 1 进行操作前要将 [1, 2] 这个标记分别传给 1 和 2

我们把中间这个作为缓冲的标记称为 lazy 标记

每区间真实账面都是不准确的,因为还有部分操作存在 lazy 当中
可是在操作每个区间时我都希望这个区间的数值是准确的
所以我们要在操作区间前把 lazy 标记向下传递
区间操作包括查询和新的修改,
这时可以写一个 pushdown 函数

void pushdown(int p, int l, int r) {
	int mid = (l + r) / 2;
	lazy[2*p] += lazy[p];
	lazy[2*p+1] += lazy[p];
	tree[2*p] += lazy[p] * (mid - l + 1);
	tree[2*p+1] += lazy[p] * (r - (mid + 1) +1);
	lazy[p] = 0;
}

在什么时候使用 pushdown()

明确 pushdown 的作用:把当前区间的 lazy 标记传给下一层的两个区间
lazy 标记的本质就是对大区间的修改,大区间下面分属的两个小区间还不知道
(大区间是知道的这个修改的,这个容易混淆,我一开始也以为 lazy 不为 0 的区间的 sum 是当前 sum 加上 区间长度*lazy,但事实上lazy不为 0 是代表他下面分属的两个小区间还没收到修改的命令,这个我表述不太清楚,但看完具体代码应该算很容易明白的)

所以在查询和修改一个区间时,我希望这个区间维护的信息是正确的
所以每次操作新区间前先把上一层的lazy传下去

区间修改的左右子树递归可以参考区间查询的左右子树递归,二者的原理是相同的

void change(int p, int l, int r, int x, int y, int num) {
	if (x<=l && r<=y) {
		tree[p] += (r - l + 1) * num;
		lazy[p] += num;
		return;
	}
	if (lazy[p] != 0) 
		pushdown(p, l, r);
	int mid = (l + r) / 2;
	if (x <= mid)
		change(p, l, mid, x, y, num);
	if (y >= mid+1)
		change(p, mid+1, r, x, y, num);
	tree[p] = tree[2*p] + tree[2*p+1];
}

区间查询的代码也与模板一的无异
只需要注意在递归下一层之前把 lazy 标记先传下去

int calc(int p, int l, int r, int x, int y) {
	if (x<=l && r<=y){
		return tree[p];
	}
	if (lazy[p] != 0) 
		pushdown(p, l, r);
	int res = 0;
	if (x <= mid)
		res += calc(2*p, l, mid, x, y)
	if (y >= mid+1)
		res += calc(2*p+1, mid+1, r, x, y)
	return res;
}

总结

下面是模板 2 的完整代码
不要学习我的#define int long long
这是个坏习惯容易被卡常数
但我说实话一道题卡常把 ll 换成 int 就过了那他卡的真没水平
非要用注意把 main 函数的返回值改成 signed

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
#define LNF 1e18
#define INF 0x3f3f3f3f
#define abs(x) llabs(x)
int arr[100001];
int tree[400001];
int lazy[400001]={0};
void build (int p, int l, int r) {
    if (l == r) {
        tree[p] = arr[l];
        return;
    }
    int mid = (l + r) / 2;
    build (2*p, l, mid);
    build (2*p+1, mid+1, r);
    tree[p] = tree[2*p] + tree[2*p+1];
}
void pushdown (int p, int l, int r){
    int mid = (l + r) / 2;
    lazy[2*p] += lazy[p];
    lazy[2*p+1] += lazy[p];
    tree[2*p] += (mid - l +1) * lazy[p];
    tree[2*p+1] += (r - (mid+1) +1) * lazy[p];
    lazy[p] = 0;
}
void change (int p, int l, int r, int x, int y, int num) {
    if (x<=l && r<=y) {
        tree[p] += num * (r-l+1);
        lazy[p] += num;
        return;
    }
    if (lazy[p] != 0){
        pushdown(p, l, r);
    }
    int mid = (l + r) / 2;
    if (x <= mid) {
        change (2*p, l, mid, x, y, num);
    }
    if (y >= mid+1) {
        change (2*p+1, mid+1, r, x, y, num);
    }
    tree[p] = tree[2*p] +tree[2*p+1];
}
int calc (int p, int l, int r, int x, int y) {
    if (x<=l && r<=y) {
        return tree[p];
    }
    if (lazy[p] != 0) {
        pushdown (p, l, r);
    }
    int res = 0;
    int mid = (l + r) / 2;
    if (x <= mid) res += calc(2*p, l, mid, x, y);
    if (y >= mid+1) res += calc(2*p+1, mid+1, r, x, y);
    return res;
}
void solve(){
    int n,q;cin >> n >> q;
    for (int i=1;i<=n;i++) cin >> arr[i];
    build (1, 1, n);
    while (q--) {
        int op;cin >> op;
        if (op == 1) {
            int x, y, num;
            cin >> x >> y >> num;
            change(1, 1, n, x, y, num);
        }else if (op == 2){
            int x, y;
            cin >> x >> y;
            cout << calc(1, 1, n, x, y) << endl;
        }
    }
}
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);cout.tie(nullptr);
    int T=1;//cin >> T;
    while (T--) solve();
}
;