一、问题描述
前面学习了无修线段树,如果我们需要修改某个区间内的数,那就要用到带修线段树。
我们先看一个例子,假设我们修改 [2, 4]:
我们应该是希望只会递归到这两个红色方框圈起来的节点,然后修改这两个节点的sum。
为什么呢?因为如果我们将 [3, 4] 下面的子树都做修改,显然每次修改都这么做,就很容易超时。
但如果不对 [3, 4] 下面的两个节点也做修改,同样有问题。假设我们在修改了 [2, 4] 之后,想要查询 [3, 3] 呢?这时候会发现,[3, 3] 的sum并没有加上修改 [2, 4] 时的值,出大问题。
所以,带修线段树使用了tag标记,为的就是记录当前节点的子树应当做对应的修改。这样即能避免每次都修改到最底层,也能在我们需要递归到更深层的时候,通过tag来顺便修改子树。
二、算法原理
每看一步可以往下翻模板代码一起看,方便理解。
1.节点信息
节点 x 比无修线段树多维护一个 tag,不同的 tag 初始值不同。
struct node {
int l, r;
int add_tag, sum;
}tr[maxn];
2.修改时如何使用tag
通过上面的问题描述我们知道,为了降低时间复杂度,我们不能对 [3, 4] 的子树也进行修改。那么我们就可以在 [3, 4] 这个节点打上 tag 标记,来表示其子树也需要进行修改。
比如令 [3, 4] 加上2,那么 add_tag = 2;
比如令 [3, 4] 减去2,那么 add_tag = -2;
3.查询时如何使用tag
我们在修改操作时,已经完成了对 sum 的修改,因此,遇到满足条件的区间,直接返回答案即可。
4.如果遇到了tag不为0的节点怎么处理
这种情况在查询和修改的时候都有可能出现。
当我们在查询或者修改的时候遇到一个节点 x 的 tag 不为0,并且此时的区间也不满足返回条件,那么我们就需要将 x 的 tag 赋值给他的子树节点,同时用这个 tag 修改子树节点的 sum 值,每次递归都进行一次这个操作,直到区间满足返回要求。
这样就完成了 tag 的传递,这种“顺便”的操作也使得 tag 被称为 lazy 标记。
5.最后一点
(1)我们在 modify() 时,假设从 x 层递归进入 y 层,并且y层满足了区间要求然后返回。
在这之后呢,我们需要进行一次 pushup( )操作,将 y 层更新后的答案用来更新 x 层的答案:
void pushup(int idx) {
tr[idx].sum = tr[idx << 1].sum + tr[idx << 1 | 1].sum;
}
(2)为什么要这么做呢?我们看一张图:
假设我们有两步操作:
modify(2, 4, 2):将 [2, 4] 加上 2;
modify(3, 3, 3):将 [3, 3] 加上 3;
最后的 sum 应该如上图黄色数字所示。
但如果没有 pushup 操作,也就是没有用修改过后的子树,更新父节点。那么两次修改之后,区间 [3, 4] 的答案会仍然为第一次修改的答案,也就是 modify(2, 4, 2)。
至此,原理讲述完成。
三、例子
URL:https://www.luogu.com.cn/problem/P3372
AC代码:
#include "bits/stdc++.h"
#define int long long
#define maxn 100007
int n, m;
struct node {
int l, r;
int tag, sum;
}tr[maxn << 2];
int a[maxn];
void pushup(int idx) {
tr[idx].sum = tr[idx << 1].sum + tr[idx << 1 | 1].sum;
}
void pushdown(int idx) {
tr[idx << 1].sum += tr[idx].tag * (tr[idx << 1].r - tr[idx << 1].l + 1);
tr[idx << 1 | 1].sum += tr[idx].tag * (tr[idx << 1 | 1].r - tr[idx << 1 | 1].l + 1);
tr[idx << 1].tag += tr[idx].tag;
tr[idx << 1 | 1].tag += tr[idx].tag;
tr[idx].tag = 0;
}
void buildtree(int l, int r, int idx) {
tr[idx].l = l, tr[idx].r = r;
if (l == r) {
tr[idx].sum = a[l]; return;
}
int mid = (l + r) >> 1;
buildtree(l, mid, idx << 1);
buildtree(mid + 1, r, idx << 1 | 1);
pushup(idx);
}
void modify(int l, int r, int k, int idx) {
if (tr[idx].l >= l and tr[idx].r <= r) {
tr[idx].sum += k * (tr[idx].r - tr[idx].l + 1);
tr[idx].tag += k;
return;
}
if (tr[idx].tag != 0) pushdown(idx);
int mid = (tr[idx].l + tr[idx].r) >> 1;
if (l <= mid) modify(l, r, k, idx << 1);
if (r > mid) modify(l, r, k, idx << 1 | 1);
pushup(idx);
}
int query(int l, int r, int idx) {
if (tr[idx].l >= l and tr[idx].r <= r) {
return tr[idx].sum;
}
if (tr[idx].tag != 0) pushdown(idx);
int mid = (tr[idx].l + tr[idx].r) >> 1;
int sum = 0;
if (l <= mid) sum += query(l, r, idx << 1);
if (r > mid) sum += query(l, r, idx << 1 | 1);
return sum;
}
signed main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0); std::cout.tie(0);
std::cin >> n >> m;
for (int i = 1; i <= n; ++ i) std::cin >> a[i];
buildtree(1, n, 1);
while (m--) {
int op, x, y, k;
std::cin >> op;
if (op == 1) {
std::cin >> x >> y >> k;
modify(x, y, k, 1);
}
else if (op == 2) {
std::cin >> x >> y;
std::cout << query(x, y, 1) << '\n';
}
}
return 0;
}