算法学习笔记(5)-- 带修线段树

一、问题描述

前面学习了无修线段树,如果我们需要修改某个区间内的数,那就要用到带修线段树。

我们先看一个例子,假设我们修改 [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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值