你也渴望线段树(鸡哥)的力量么!!
在开始该死的正文之前,先流水账一下6.20的悲惨遭遇。牛客刷题计划才开始了两天,我就顿感萎靡不振,于是6.19进行了划水计划(已经开启了一点点线段树),6.20计划活力满满开启线段树之旅的,上午补测了一个物理小测(只有80,damn,man) , 中午小眠一波准备开启线段树之旅的。谁曾想被导员拉去开党会到下午三点,然后被校考评拉去机械的做了俩ing小时流水线盖章工作(东林的校考评还是一个很好的组织,人文关怀是很好的),到实验室又莫名其妙刷了一个小时抖音,疲惫感涌上心头,但由于负罪感充斥这自己,看了一点点线段树 (被扫描线劝退,damn,man),也写不出什么代码,但好在原理基本上懂了,遗留了一个关于modify递归后区间的小问题不理解。晚上莫名跟着实验室满载而归的大哥们一起划水到两点,无比后悔
今日6.21起床已是9点+,决定翘课完成线段树理论阶段,好在效率比较高,也可能是库迪咖啡物理外挂的buff加成的缘故,有效学习时间个人感觉很多也很爽,到下午三点疲惫再次袭来,因为晚上有组队赛决定写完这篇博客出去休息一下晚上再回归
此篇博客仅记录 有乘有加 的线段树的操作
虚假的困难
题意便是如此,分析请看
是的,相较于模板线段树,多出了个乘法操作,如何维护信息,很重要,于是给出懒标记add与mul
接下来便是考虑如何用懒标记更新sum,以及如何更新懒标记的问题,便遇到这个懒标记优先级的问题,具体分析不在赘述,看图吧(打字太damn的麻烦了)
如此之后,便可以开始操作了
考虑建树 build
void build(int u, int l, int r ) {
if (l == r) {
tr[u] = {l, r, w[r], 0, 1};
} else {
tr[u] = {l, r, 0, 0, 1};
int mid = l + r >> 1;
build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
pushup(u);
}
}
没啥变化,就是初始的时候,mul是1,add是0;
不懂建议手推一下先add后mul和先mul后add那俩图就懂了
考虑 modify
void modify(int u, int l, int r, int add, int mul) {
if (tr[u].l >= l && tr[u].r <= r) {
cal(tr[u], add, mul);
} else {
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if (l <= mid)
modify(u << 1, l, r, add, mul);
if (r > mid)
modify(u << 1 | 1, l, r, add, mul);
pushup(u);
}
}
哦!不太一样,cal()是什么??
cal ( ) 函数其实就是封装一下计算儿子的sum,并且传导lazy的操作
void pushup(int u) {
tr[u].sum = (tr[u << 1].sum + tr[u << 1 | 1].sum) % p;
}
void cal(node &t, int add, int mul) {
// t本身自带的add,和mul在他传下来的时候就已经被算过了,加到sum里面了,
// 没算的是下一层的,所以这一层只需要算新加进来的add和mul
// 同时更新当前层的add和mul就可以了;将来会传下去的
// 只有pushdown的时候才会下传懒标记别手贱
t.sum = ((LL)t.sum * mul + (LL)add * (t.r - t.l + 1) % p) % p;
t.mul = (t.mul * mul) % p;
t.add = ((LL)(t.add) * mul + add) % p;
}
void pushdown(int u) {
cal(tr[u << 1], tr[u].add, tr[u].mul);
cal(tr[u << 1 | 1], tr[u].add, tr[u].mul);
//计算完之后,下传完lazy之后,别忘记tr[u] 的lazy归0
tr[u].add = 0, tr[u].mul = 1;
}
连带pushup pushdown一并给出,细节思考在注视里面
(哪里会用到cal呢?,显然pushdown:先计算,然后下传lazy标记(依赖父亲的add与mul),最后将原lazy打成初始状态)
(~modify也用到啊!,显然modify:先计算,然后更新!lazy标记(依赖输入的add与mul)! 区别在于:pushdown之所以叫做下传,依赖的是爸爸的add与mul , 而modfiy之所以叫修改,依赖的是给出的要修改的add与mul!所以modify改完没有可以清空的lazy,而pushdown可是要清空爸爸的lazy 哦!!!~)
比较重要就不加删除线了
如此,考虑query手到擒来
LL query(int u, int l, int r ) {
if (tr[u].l >= l && tr[u].r <= r)
return tr[u].sum;
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
LL res = 0;
if (l <= mid)
res += (query(u << 1, l, r) % p);
if (r > mid)
res += (query(u << 1 | 1, l, r) % p);
return res % p;
}
不解释
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int w[N];
int n, m, p;
typedef long long LL;
struct node {
int l, r;
LL sum, add, mul;
};
node tr[N * 4];
void pushup(int u) {
tr[u].sum = (tr[u << 1].sum + tr[u << 1 | 1].sum) % p;
}
void cal(node &t, int add, int mul) {
// t本身自带的add,和mul在他传下来的时候就已经被算过了,加到sum里面了,
// 没算的是下一层的,所以这一层只需要算新加进来的add和mul
// 同时更新当前层的add和mul就可以了;将来会传下去的
// 只有pushdown的时候才会下传懒标记别手贱
t.sum = ((LL)t.sum * mul + (LL)add * (t.r - t.l + 1) % p) % p;
t.mul = (t.mul * mul) % p;
t.add = ((LL)(t.add) * mul + add) % p;
}
void pushdown(int u) {
cal(tr[u << 1], tr[u].add, tr[u].mul);
cal(tr[u << 1 | 1], tr[u].add, tr[u].mul);
//计算完之后,下传完lazy之后,别忘记tr[u] 的lazy归0
tr[u].add = 0, tr[u].mul = 1;
}
void build(int u, int l, int r ) {
if (l == r) {
tr[u] = {l, r, w[r], 0, 1};
} else {
tr[u] = {l, r, 0, 0, 1};
int mid = l + r >> 1;
build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
pushup(u);
}
}
void modify(int u, int l, int r, int add, int mul) {
if (tr[u].l >= l && tr[u].r <= r) {
cal(tr[u], add, mul);
} else {
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if (l <= mid)
modify(u << 1, l, r, add, mul);
if (r > mid)
modify(u << 1 | 1, l, r, add, mul);
pushup(u);
}
}
LL query(int u, int l, int r ) {
if (tr[u].l >= l && tr[u].r <= r)
return tr[u].sum;
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
LL res = 0;
if (l <= mid)
res += (query(u << 1, l, r) % p);
if (r > mid)
res += (query(u << 1 | 1, l, r) % p);
return res % p;
}
int main() {
// ios::sync_with_stdio(0);
cin >> n >> p;
for (int i = 1; i <= n; i++)
cin >> w[i];
cin >> m;
build(1, 1, n);
while (m--) {
int op, l, r, d;
cin >> op >> l >> r;
if (op == 1) {
cin >> d;
modify(1, l, r, 0, d);
}
if (op == 2) {
cin >> d;
modify(1, l, r, d, 1);
}
if (op == 3) {
cout << query(1, l, r) % p << endl;
}
}
}
再补一下每个函数的小思路吧
pushup :
儿子算爸爸
pushdown:
传爸爸参数,用爸爸信息,算儿子sum,下传爸爸lazy给儿子,清空爸爸lazy
build :
叶子节点存信息,递归建树,更新爸爸
cal:
用给出的add与mul计算当前层sum,更改当前层lazy标记
modify :
真包含区间,直接cal,否则下传lazy标记!取中点分别递归
query:
这包含区间,直接return,否则下传lazy标记!取重点分别递归
有一点点累,但是应该在变强