P3373(线段树2)

线段树2

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

将某区间每一个数乘上 x

将某区间每一个数加上 x

求出某区间每一个数的和

输入格式

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

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

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

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

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

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

输入

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

输出
17
2

思路

线段树区间乘法 + 区间加法。由于区间更新需要采用延迟更新lazy操作,所以在更新区间乘法和加法之间有一些需要注意的东西。题看起来很简单但是写起来确实有点不好下手,扮猪吃老虎的好题。加强了我对线段树的理解。

  1. 加法操作直接更新即可

  2. 乘法操作就不能了,如果当前节点有加法延迟add标记。那么准确的延迟操作需要先将加法向下延迟更新了才能更新乘法操作:左区间(s[k<<1] + add[k] * (m - l+1)) * mul[k],m = (l+r)/2。利用分配率将这个表达式拆开就可以变成s[k<<1] * mul[k] + add[k] * mul[k] * (m - l + 1),右区间同理。那么add[k] * mul[k]操作就可以在更新乘法的时候直接给add乘上,最后在向下延迟更新。

  3. 最开始的疑点在乘法的时候为啥要add[k] *= mul[k]而不能在向下更新的时候做add[k] *= mul[k]呢?如果先给区间[1,5] 每个数加3,那么add[k] = 3。然后再给区间[1,5] 每个数乘5,那么mul[k] = 3。最后给区间[1,5] 每个数再加3,那么add[k] = 6。如果按照在向下更新的时候做add[k] *= mul[k]那么等于30,如果是在遇到乘法的时候更新add[k] *= mul[k],那么add[k] = 3 * 5 + 3 = 18。显然这种方法是正确的,错在数学运算上了。其他的问题自然迎刃而解~

非常好的一道题加深了我对线段树区间更新的理解,虽然想了很久才想清楚但是不亏

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 1e5+5;
#define lson k<<1,l,m
#define rson k<<1|1,m+1,r
#define ll long long
ll s[maxn<<2],add[maxn<<2],mul[maxn<<2];
int a[maxn],p;
inline void build(int k,int l,int r)
{
	add[k] = 0;
	mul[k] = 1;
	if(l == r){
		s[k] = a[l];
		return ;
	}
	int m = (l+r)>>1;
	build(lson);
	build(rson);
	s[k] = s[k<<1] + s[k<<1|1];
}
inline void push_down(int k,int l,int r)
{
	int m = (l+r)>>1;
	s[k<<1] = (s[k<<1] * mul[k] + add[k] * (m-l+1))%p;
	s[k<<1|1] = (s[k<<1|1] * mul[k] + add[k] * (r-m))%p;
	add[k<<1] = (add[k<<1] * mul[k] + add[k])%p;
	add[k<<1|1] = (add[k<<1|1] * mul[k] + add[k])%p;
	mul[k<<1] = mul[k<<1] * mul[k] % p;
	mul[k<<1|1] = mul[k<<1|1] * mul[k] % p;
	add[k] = 0;
	mul[k] = 1;
}
inline void update(int k,int l,int r,int ql,int qr,int ans,int op)
{
	if(qr < l || r < ql){
		return ;
	}
	if(ql <= l && r <= qr){
		if(op == 1){			//乘法 
			mul[k] = (mul[k] * ans) % p;
			add[k] = (add[k] * ans) % p;		//必须更新加法。
			s[k] = (s[k] * ans) % p;
		}
		if(op == 2){					//加法 
			add[k] = (add[k] + ans)%p;
			s[k] = (s[k] + (r-l+1)*ans)%p;
		}
		return ;
	}
	push_down(k,l,r); 
	int m = (l+r)>>1;
	update(lson,ql,qr,ans,op);
	update(rson,ql,qr,ans,op);
	s[k] = s[k<<1] + s[k<<1|1];
}
ll query(int k,int l,int r,int ql,int qr)
{
	if(qr < l || r < ql){
		return 0;
	}
	if(ql <= l && r <= qr){
		return s[k];
	}
	push_down(k,l,r);
	int m = (l+r)>>1;
	int s1 = query(lson,ql,qr)%p;
	int s2 = query(rson,ql,qr)%p;
	return (s1+s2)%p;
}
int main()
{
	int n,m;
	while(~scanf("%d%d%d",&n,&m,&p)){
		for(int i = 1;i <= n;i++){
			scanf("%d",&a[i]);
		}
		build(1,1,n);
		int c,x,y,z;
		while(m--){
			scanf("%d",&c);
			if(c < 3){
				scanf("%d%d%d",&x,&y,&z);
				update(1,1,n,x,y,z,c);	
			}
			else{
				scanf("%d%d",&x,&y);
				ll ans = query(1,1,n,x,y);
				printf("%lld\n",ans);
			}
		} 
	}
	return 0;
}

愿你走出半生,归来仍是少年~

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值