P3373 【模板】线段树2 题解

【题目链接】P3373 【模板】线段树 2

【思路】

本题复杂在于有两个优先级不同的区间修改操作。考虑遵循乘法优先,那么维护两个懒标记mul(乘)和add(加),打加法标记按照普通方法,打乘法标记要注意把加法标记也乘一下要乘的数。加法标记的下传,要先把原标记乘上乘法标记(乘法优先)

举个例子:

假设一个长度为5的区间的和为25,现要把区间每个数加上5再乘上5,那么打乘法标记时,加法标记就应变为5*5=25,那么维护该区间的值时,这个区间的值就应该是25*5+5*5*5=250,类似于乘法分配律的思想。先乘后加也是一样的推导:打乘法标记时,加法标记变为0*5=0,后来加法打上标记5,维护区间值时,这个区间的值就应该是25*5+5*5=150。

【Code】

#include<iostream>
#include<cstdio>
using namespace std;
struct Segment_Tree{
	long long sum;
	long long mul,add;
}tree[500000];
long long a[100100],kk;
int p,n,m,op,ll,rr;
inline void build(int k,int l,int r){
	tree[k].add=0;
	tree[k].mul=1;
	if(l==r){
		tree[k].sum=a[l]%p;
		return;
	}
	int mid=(l+r)>>1;
	build(k<<1,l,mid);
	build(k<<1|1,mid+1,r);
	tree[k].sum=(tree[k<<1].sum+tree[k<<1|1].sum)%p;
}
inline void pushdown(int k,int l,int r){
	int mid=(l+r)>>1;
	tree[k<<1].sum=(tree[k<<1].sum*tree[k].mul+tree[k].add*(mid-l+1))%p;
	tree[k<<1|1].sum=(tree[k<<1|1].sum*tree[k].mul+tree[k].add*(r-mid))%p;
	if(tree[k].add){
		tree[k<<1].add=(tree[k<<1].add*tree[k].mul+tree[k].add)%p;
		tree[k<<1|1].add=(tree[k<<1|1].add*tree[k].mul+tree[k].add)%p;
		tree[k].add=0;
	}
	if(tree[k].mul!=1){
		tree[k<<1].mul=(tree[k<<1].mul*tree[k].mul)%p;
		tree[k<<1|1].mul=(tree[k<<1|1].mul*tree[k].mul)%p;
		tree[k].mul=1;
	}
}
inline long long Ask(int k,int l,int r,int x,int y){
	if(x>r||l>y)return 0;
	if(x<=l&&y>=r)return tree[k].sum;
	pushdown(k,l,r);
	int mid=(l+r)>>1;
	long long res=0;
	if(x<=mid)res+=Ask(k<<1,l,mid,x,y);
	if(y>mid)res+=Ask(k<<1|1,mid+1,r,x,y);
	return res%p;
}
inline void modify_add(int k,int l,int r,int x,int y,long long v){
	if(y<l||r<x)return;
	if(x<=l&&r<=y){
		tree[k].add=(tree[k].add+v)%p;
		tree[k].sum=(tree[k].sum+v*(r-l+1))%p;
		return;
	}
	pushdown(k,l,r);
	int mid=(l+r)>>1;
	if(x<=mid)modify_add(k<<1,l,mid,x,y,v);
	if(y>mid)modify_add(k<<1|1,mid+1,r,x,y,v);
	tree[k].sum=(tree[k<<1].sum+tree[k<<1|1].sum)%p;
}
inline void modify_mul(int k,int l,int r,int x,int y,long long v){
	if(y<l||r<x)return;
	if(x<=l&&r<=y){
		tree[k].mul=(tree[k].mul*v)%p;
		tree[k].sum=(tree[k].sum*v)%p;
		tree[k].add=(tree[k].add*v)%p;
		return;
	}
	pushdown(k,l,r);
	int mid=(l+r)>>1;
	if(x<=mid)modify_mul(k<<1,l,mid,x,y,v);
	if(y>mid)modify_mul(k<<1|1,mid+1,r,x,y,v);
	tree[k].sum=(tree[k<<1].sum+tree[k<<1|1].sum)%p;
}
int main(){
	scanf("%d%d%d",&n,&m,&p);
	for(register int i=1;i<=n;i++)scanf("%lld",&a[i]);
	build(1,1,n);
	for(register int i=1;i<=m;i++){
		scanf("%d",&op);
		if(op==1){
			scanf("%d%d%lld",&ll,&rr,&kk);
			modify_mul(1,1,n,ll,rr,kk);
		}
		else{
			if(op==2){
				scanf("%d%d%lld",&ll,&rr,&kk);
				modify_add(1,1,n,ll,rr,kk);
			}
			else{
				scanf("%d%d",&ll,&rr);
				printf("%lld\n",Ask(1,1,n,ll,rr));
			}
		}
	}
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值