线段树模板
对于含有多种区间操作,并且这些区间操作的先后顺序有所影响时,我们需要规定一个合适的运算顺序,使得懒标签的维护更为简单,更易维护。
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);
}
}
}