【模板】线段树 2

题目描述

如题,已知一个数列,你需要进行下面三种操作:

  • 将某区间每一个数乘上 x x x

  • 将某区间每一个数加上 x x x

  • 求出某区间每一个数的和

输入格式

第一行包含三个整数 n , m , p n,m,p n,m,p,分别表示该数列数字的个数、操作的总个数和模数。

第二行包含 n n n 个用空格分隔的整数,其中第 i i i 个数字表示数列第 i i i 项的初始值。

接下来 m m m 行每行包含若干个整数,表示一个操作,具体如下:

操作 1 1 1: 格式:1 x y k 含义:将区间 [ x , y ] [x,y] [x,y] 内每个数乘上 k k k

操作 2 2 2: 格式:2 x y k 含义:将区间 [ x , y ] [x,y] [x,y] 内每个数加上 k k k

操作 3 3 3: 格式:3 x y 含义:输出区间 [ x , y ] [x,y] [x,y] 内每个数的和对 p p p 取模所得的结果

输出格式

输出包含若干行整数,即为所有操作 3 3 3 的结果。

样例 #1

样例输入 #1

5 5 38
1 5 4 2 3
2 1 4 1
3 2 5
1 2 4 2
2 3 5 5
3 1 4

样例输出 #1

17
2

提示

【数据范围】

对于 30 % 30\% 30% 的数据: n ≤ 8 n \le 8 n8 m ≤ 10 m \le 10 m10
对于 70 % 70\% 70% 的数据: n ≤ 1 0 3 n \le 10^3 n103 m ≤ 1 0 4 m \le 10^4 m104
对于 100 % 100\% 100% 的数据: n ≤ 1 0 5 n \le 10^5 n105 m ≤ 1 0 5 m \le 10^5 m105

除样例外, p = 571373 p = 571373 p=571373

(数据已经过加强qwq)

样例说明:

故输出应为 17 17 17 2 2 2 40   m o d   38 = 2 40 \bmod 38 = 2 40mod38=2

解题思路:

关于线段树,这里简单说明一下

首先是线段树的结构,因为用指针存储树的结构过于麻烦,所以用数组进行树的结构存储

初始化,为根节点分配索引 1 1 1

设根节点索引为root,左子树索引为left,右子树索引为right

则有left = root * 2right = root * 2 + 1

然后你会发现每层最后一个节点的索引分别为 1 1 1 3 3 3 7 7 7 15 15 15

也就是 2 1 − 1 2^1-1 211 2 2 − 1 2^2 - 1 221 2 3 − 1 2^3 - 1 231 2 4 − 1 2^4 - 1 241…,挺神奇的

所以我们为线段树维护的节点数量为区间长度 ∗ 4 *4 4

关于节点的数据,每个节点维护一段数组区间

左子树和右子树各自维护根节点的一半区间

维护的可以是区间的最大/最小值、区间和等

然后关于线段树的三大操作:建立、更新、查询,这里不详细展开

其核心思路就是先不断向下搜索确定子节点,然后递归更新根节点

如果直接看代码不太懂的话,一会我会推荐一篇我认为写得很好的算法说明

最后是比较难懂的懒惰标记(就是代码中的mul_tagadd_tag

它的功能就是像名字一样,是用来偷懒的

更新到某个节点(该节点维护的区间为更新区间的真子集)之后停止更新,而是打上标记

如果之后需要用到它的子节点,那么就继续更新,这样可以减少操作量,提高效率

生动形象的话就是“工作你检查到哪里我就做到哪里”

这里是一个讲解的非常好的线段树说明(我就是从这里学的):线段树

本题有点难是因为有两个懒惰标记:加法标记和乘法标记

我们需要确定更新子树的时候这两个标记的操作顺序

从而决定这两个标记本身的更新顺序

比较简单的是先乘法后加法的顺序更新子树,先加后乘可以自行尝试

如果是操作 1 1 1,那么将两个标记同时乘上操作数即可

如果是操作 2 2 2,只需要把加法标记加上操作数,甚至不需要更新乘法标记

这里需要注意的一点是所有乘法标记最开始需要初始化为 1 1 1

但是在运算过程中,由于取模操作,乘法标记可能为 0 0 0

所以,懒惰标记是否传递的判断应该是mul_tag != 1

其余判断均会导致一些更新错误

那么,AC代码如下

#include <iostream>
using namespace std;
const int max_n = int(1e5);
const int max_m = int(1e5);

int n, m;
long long p;
long long num_arr[max_n + 1];
long long tree[max_n * 4 + 1];
long long mul_tag[max_n * 4 + 1];
long long add_tag[max_n * 4 + 1];


void push_down(int index, int l, int r) {
	if (mul_tag[index] != 1 || add_tag[index]) {
		long long mul = mul_tag[index];
		long long add = add_tag[index];
		int m = l + ((r - l) >> 1);

		tree[index << 1] = ((tree[index << 1] * mul) + add * (long long)(m - l + 1)) % p;
		mul_tag[index << 1] = (mul * mul_tag[index << 1]) % p;
		add_tag[index << 1] = (add + add_tag[index << 1] * mul) % p;

		tree[(index << 1) + 1] = ((tree[(index << 1) + 1] * mul) + add * (long long)(r - m)) % p;
		mul_tag[(index << 1) + 1] = (mul * mul_tag[(index << 1) + 1]) % p;
		add_tag[(index << 1) + 1] = (add + add_tag[(index << 1) + 1] * mul) % p;

		mul_tag[index] = 1;
		add_tag[index] = 0;
	}
}

void build_tree(int index, int l, int r) {
	if (l == r) {
		tree[index] = num_arr[l] % p;
		return;
	}
	else {
		int m = l + ((r - l) >> 1);
		build_tree(index << 1, l, m);
		build_tree((index << 1) + 1, m + 1, r);
		tree[index] = (tree[index << 1] + tree[(index << 1) + 1]) % p;
	}
}

void update_1(int index, int l, int r, const long long mul, const int L, const int R) {
	if (L <= l && r <= R) {
		tree[index] = (mul * tree[index]) % p;
		mul_tag[index] = (mul * mul_tag[index]) % p;
		add_tag[index] = (mul * add_tag[index]) % p;
		return;
	}
	else {
		push_down(index, l, r);

		int m = l + ((r - l) >> 1);
		if (L <= m)
			update_1(index << 1, l, m, mul, L, R);
		if (m + 1 <= R)
			update_1((index << 1) + 1, m + 1, r, mul, L, R);

		tree[index] = (tree[index << 1] + tree[(index << 1) + 1]) % p;
	}
}

void update_2(int index, int l, int r, const long long add, const int L, const int R) {
	if (L <= l && r <= R) {
		tree[index] = ((long long)(r - l + 1) * add + tree[index]) % p;
		add_tag[index] = (add_tag[index] + add) % p;
		return;
	}
	else {
		push_down(index, l, r);

		int m = l + ((r - l) >> 1);
		if (L <= m)
			update_2(index << 1, l, m, add, L, R);
		if (m + 1 <= R)
			update_2((index << 1) + 1, m + 1, r, add, L, R);

		tree[index] = (tree[index << 1] + tree[(index << 1) + 1]) % p;
	}
}

long long search(int index, int l, int r, const int L, const int R) {
	if (L <= l && r <= R) {
		return tree[index];
	}
	else {
		push_down(index, l, r);

		int m = l + ((r - l) >> 1);
		long long left_ret = 0, right_ret = 0;

		if (L <= m)
			left_ret = search(index << 1, l, m, L, R);
		if (m + 1 <= R)
			right_ret = search((index << 1) + 1, m + 1, r, L, R);

		return (left_ret + right_ret) % p;
	}
}

int main() {
	for (int i = 1; i <= max_n * 4; i++) mul_tag[i] = 1;

	cin >> n >> m >> p;
	for (int i = 1; i <= n; i++) cin >> num_arr[i];

	build_tree(1, 1, n);

	int q[3];
	long long num;
	for (int i = 1; i <= m; i++) {
		cin >> q[0];
		if (q[0] == 1) {
			cin >> q[1] >> q[2] >> num;
			update_1(1, 1, n, num % p, q[1], q[2]);
		}
		else if (q[0] == 2) {
			cin >> q[1] >> q[2] >> num;
			update_2(1, 1, n, num % p, q[1], q[2]);
		}
		else {
			cin >> q[1] >> q[2];
			cout << search(1, 1, n, q[1], q[2]) << endl;
		}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WitheredSakura_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值