你也渴望线段树的力量么

你也渴望线段树(鸡哥)的力量么!!

在开始该死的正文之前,先流水账一下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标记!取重点分别递归

有一点点累,但是应该在变强

你也渴望ACM~~鸡哥~~的力量么!!!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值