【模板】线段树 区间加+乘,区间求和 (模板题:洛谷P3373)

题目描述

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

1.将某区间每一个数加上x

2.将某区间每一个数乘上x

3.求出某区间每一个数的和

输入输出格式

输入格式:

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

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

接下来M行每行包含3或4个整数,表示一个操作,具体如下:

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

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

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

输出格式:

输出包含若干行整数,即为所有操作3的结果。

输入输出样例

输入样例#1:
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
输出样例#1:
17
2






说明

时空限制:1000ms,128M

数据规模:

对于30%的数据:N<=8,M<=10

对于70%的数据:N<=1000,M<=10000

对于100%的数据:N<=100000,M<=100000

(数据已经过加强^_^)

样例说明:

故输出应为17、2(40 mod 38=2)




#include <iostream>
#include <cstdio>
#define ll long long
using namespace std;

ll n,m,flag,x,y,v,p;
ll tim[500000],a[510000],add[510000],sum[510000];

void pushup(ll rt){                //修改当前节点的值 
	sum[rt]=(sum[rt<<1]+sum[rt<<1|1])%p;
}

void build(ll l,ll r,ll rt){       //递归建树 
	if (l==r){
		sum[rt]=a[l];return ;
	}
	ll m=(l+r)>>1;
	build(l,m,rt<<1);
	build(m+1,r,rt<<1|1);
	pushup(rt);
}

void pushdown(ll rt,ll ln,ll rn){  //下推标记 
	if (add[rt]==0 && tim[rt]==1) return ;//没有标记就退出 
	if (ln+rn==1) return ;         	      //是叶子节点也退出 
	
	//根据c*(ax+b)=acx+bc,得出修改节点的运算 
	sum[rt<<1]=(sum[rt<<1]*tim[rt]+add[rt]*ln)%p;
	sum[rt<<1|1]=(sum[rt<<1|1]*tim[rt]+add[rt]*rn)%p;	
		
	//如上的运算,将加法标记下推(受乘法影响有顺序) 
	add[rt<<1]=(add[rt<<1]*tim[rt]+add[rt])%p;
	add[rt<<1|1]=(add[rt<<1|1]*tim[rt]+add[rt])%p;
	
	add[rt]=0;//清空上面节点的标记 

    	//如上的运算,将乘法标记下推(无顺序影响) 
	tim[rt<<1]=(tim[rt<<1]*tim[rt])%p;
	tim[rt<<1|1]=(tim[rt<<1|1]*tim[rt])%p;
		
	tim[rt]=1;//清空上面节点的标记 
}

void update1(ll L,ll R,ll v,ll l,ll r,ll rt){//乘法修改 
	ll m=(l+r)>>1;
	pushdown(rt,m-l+1,r-m);	    //首先执行下推操作 
	
	if (L<=l && r<=R){          //如果完全处于操作区间 
		sum[rt]=(sum[rt]*v)%p;  //则先修改当前节点 
		tim[rt]*=v;             //并打上标记 
		return ;
	}
	
	//否则先递归以下的节点,最后修改当前节点的值 
	if (L<=m) update1(L,R,v,l,m,rt<<1);
	if (R> m) update1(L,R,v,m+1,r,rt<<1|1);
	pushup(rt);
}

void update2(ll L,ll R,ll v,ll l,ll r,ll rt){//加法修改 
	ll m=(l+r)>>1;
	pushdown(rt,m-l+1,r-m);	
	
	if (L<=l && r<=R){
		sum[rt]=(sum[rt]+v*(r-l+1))%p;
		add[rt]+=v;
		return ;
	}
	
	if (L<=m) update2(L,R,v,l,m,rt<<1);
	if (R> m) update2(L,R,v,m+1,r,rt<<1|1);
	pushup(rt);
}

ll ask(ll L,ll R,ll l,ll r,ll rt){   //问询区间[L,R]的和 
	if (L<=l && r<=R){         //如果有完整包含的,则直接返回 
		return sum[rt];
	}
	ll m=(l+r)>>1;             //先执行下推操作 
	pushdown(rt,m-l+1,r-m); 
	
	ll s=0;                    //递归求和 
	if (L<=m) s=(s+ask(L,R,l,m,rt<<1))%p;
	if (R> m) s=(s+ask(L,R,m+1,r,rt<<1|1))%p;
	return s;
}


int main(){
	scanf("%lld%lld%lld",&n,&m,&p);
	for (ll i=1;i<=4*n;++i) 
		tim[i]=1;
	for (ll i=1;i<=n;++i)
		scanf("%lld",&a[i]);
		
	build(1,n,1);
	
	for (ll i=1;i<=m;++i){
		scanf("%lld",&flag);
		
		if (flag==1){
			scanf("%lld%lld%lld",&x,&y,&v);
			update1(x,y,v,1,n,1);
		}else
		if (flag==2){
			scanf("%lld%lld%lld",&x,&y,&v);
			update2(x,y,v,1,n,1);
		}else
		if (flag==3){
			scanf("%lld%lld",&x,&y);
			printf("%lld\n",ask(x,y,1,n,1));
		}
	}
	return 0;
}


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值