线段树模板(先后顺序的处理)

线段树模板

​ 对于含有多种区间操作,并且这些区间操作的先后顺序有所影响时,我们需要规定一个合适的运算顺序,使得懒标签的维护更为简单,更易维护。

P3373 【模板】线段树 2 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

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

  • 将某区间每一个数乘上 x;
  • 将某区间每一个数加上 x;
  • 求出某区间每一个数的和。

​ 比较公式化,唯一需要处理的是讨论加和乘法的先后顺序。

1.加法优先, 则left->val =((left->val+root->add)*root->mul)%m

如果对add进行更改,那么mul也要进行等比例的修改,这样会产生分数,从而带来精度损失

2.乘法优先,则left->val = ((left->val * root->mul)+root->add * (r-l+1))%m

容易发现,无论对哪个标签进行修改都是容易的,所以使用乘法优先的算法

此外还有一些小细节要注意:每次准备向下搜索时,都要push_down一下,对于懒标签的维护,由于存在先后顺序,在对乘法标签进行修改后,加法标签也要进行修改(类似结合律和分配律之类的)

​ left->val =(((left->val * root->mul) +root->add* (r-l+1))* mul’+add’)%m

#include<iostream>
#include<algorithm>

using namespace std;

#define inf (1e15)
#define MAXN (100005)

//注意到可以用两个懒标签add和mul进行传递
//但要考虑更新区间时两种运算的优先级
//1.加法优先, 则left->val =((left->val+root->add)*root->mul)%m
// 如果对add进行更改,那么mul也要进行等比例的修改,这样会产生分数,从而带来精度损失
//2.乘法优先,则left->val = ((left->val*root->mul)+root->add*(r-l+1))%m
// 容易发现,无论对哪个标签进行修改都是容易的,所以使用乘法优先的算法 
//此外模m的问题,容易发现对于标签而言也是成立的
int m;
int nums[MAXN];

struct Node {
	int l, r;
	long long val, add, mul;
	Node* left, * right;

	void Up() {
		val = (left->val + right->val)%m;
	}

	void Down() {
		int mid = l + r >> 1;
		left->val = ((left->val * mul) + add * (mid - l + 1))%m;
		right->val = ((right->val * mul) + add * (r - mid ))%m;
		//对于标签的维护,对于乘法是容易的,直接乘起来取模即可
		left->mul = (left->mul * mul) % m;
		right->mul = (right->mul * mul) % m;
		//对于加法,情况稍微有一点复杂,由于子区间乘了mul,则之前的add需要乘上mul,再加上root的add
		left->add = (left->add * mul + add) % m;
		right->add = (right->add * mul + add) % m;
		//更新完成,重置父区间标签
		mul = 1;
		add = 0;
	}
	作为说明,此处加上先计算加法的顺序
	//left->val = ((left->val + add) * mul) % m;
	//left->add = (left->add + add) % m;
	由于子区间先加add,则之前的mul需要规整一下,使得 val' *mul' =val *mul,其中val=val'+add,可以解出
	容易发现这个在取模显然会出错,就不写了,我们可以同除以val然后得到一个系数,显然是分数
	//left->mul = ()

	bool In(int L, int R) {
		return L <= l && R >= r;
	}

	bool Out(int L, int R) {
		return R<l || L>r;
	}

	void add_update(int L, int R, long x) {
		if (In(L, R)) {
			add = (add + x) % m;
			val = (val + x * (r - l + 1)) % m;
		}
		else if (!Out(L, R)) {
			int mid = (l + r) >> 1;
			Down();
			left->add_update(L, R, x);
			right->add_update(L, R, x);
			Up();
		}
		return;
	}

	void mul_update(int L, int R, long x) {
		if (In(L, R)) {
			mul = (mul * x) % m;
			//由于我们先计算的是乘法,所以在更改mul后,add也会改变,需要对add进行修改!
			add = (add * x) % m;
			val = (val * x) % m;
		}
		else if (!Out(L, R)) {
			int mid = (l + r) >> 1;
			Down();
			left->mul_update(L, R, x);
			right->mul_update(L, R, x);
			Up();
		}
		return;
	}

	long long query(int L, int R) {
		if (In(L, R)) {
			return val;
		}
		else if (!Out(L, R)) {
			Down();
			long long ans = (left->query(L, R) + right->query(L, R))%m;
			return ans;
		}
		return 0;//一定要写!否则一直查询下去
	}
};

Node SegTree[MAXN << 1];
Node* p = SegTree;

Node* build(int L, int R) {
	Node* q = p++;//从1开始
	q->l = L;
	q->r = R;
	q->mul = 1;
	q->add = 0;
	if (L != R) {
		int mid = L + R >> 1;
		q->left = build(L, mid);
		q->right = build(mid + 1, R);
		q->Up();
	}
	else q->val = nums[L];
	return q;
}

int main() {
	int n, q;
	cin >> n >> q >> m;
	for (int i = 1; i <= n; i++) {
		cin >> nums[i];
	}
	Node* root = build(1, n);
	int x,y,op,k;
	for (int i = 1; i <= q; i++) {
		cin >> op >> x >> y;
		if (op == 3) {
			cout << root->query(x, y) << endl;
		}
		else {
			cin >> k;
			if (op == 2) {
				root->add_update(x, y, k);
			}
			else root->mul_update(x, y, k);
		}
	}
}
  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值