洛谷P3373 【模板】线段树 2

https://www.luogu.org/problem/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)

思路

既有乘法,既有加法,那么设立两个算法标记,但是某个区间的乘法和加法顺序先后不同,得到的答案也会不同,我们先对标记进行一些预处理。如果要给[l,r]区间添加乘法标记flag1,而且[l,r]区间已经有了加法标记flag,那么flag=flag*flag1,对于这部分乘法来说,只针对目前的加法标记flag,后面继续添加的乘法标记针对的已经是新的加法标记flag,他们对子区间的值和乘法标记并没有什么影响。

乘法标记的初始值为1,但是在运算取余的过程中可能变成0,这会出错,把0改为模值就可以了,一直错在这

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<vector>
#include<map>
#include<set>
#include<queue>
#include<stack>
#define rep(i,s,e) for(int i=s;i<=e;i++)
#define rep1(i,s,e) for(int i=s;i<e;i++)
#define rep2(i,s,e,c) for(int i=s;i>=e;i--)
#define pfi(x) printf("%d\n",x)
#define pfl(x) printf("%lld\n",x)
#define pfn() printf("\n")
#define pfs() printf(" ")
#define sfi(x) scanf("%d",&x)
#define sfi1(x,y) scanf("%d%d",&x,&y)
#define sff(x) scanf("%lf",&x)
#define sfl(x) scanf("%lld",&x)
#define memset1(x) memset(x,0,sizeof(x))
using namespace std;
typedef long long ll;
const int MAX = 1e5 + 50;
const int mod = 996873654;
ll n,M,p;
ll ar[MAX];
struct node {
	ll l,r,w;
	ll flag,flag1;
}Ltree[MAX<<2];
void creat(ll l,ll r,ll k){
	Ltree[k].l=l;
	Ltree[k].r=r;
	Ltree[k].flag1=1;
	Ltree[k].flag=0;
	if(l==r){
		Ltree[k].w=ar[l]%p;
		return;
	}
	ll m=(l+r)>>1;
	creat(l,m,k<<1);
	creat(m+1,r,k<<1|1);
	Ltree[k].w=(Ltree[k<<1].w+Ltree[k<<1|1].w)%p;
}
void down(ll k){
    Ltree[k<<1].flag=(Ltree[k].flag+Ltree[k<<1].flag*Ltree[k].flag1)%p;
    Ltree[k<<1|1].flag=(Ltree[k].flag+Ltree[k<<1|1].flag*Ltree[k].flag1)%p;
    Ltree[k<<1].flag1=Ltree[k<<1].flag1*Ltree[k].flag1%p;
    Ltree[k<<1|1].flag1=Ltree[k<<1|1].flag1*Ltree[k].flag1%p;
    if(!Ltree[k<<1].flag1) Ltree[k<<1].flag1=p;
    if(!Ltree[k<<1|1].flag1) Ltree[k<<1|1].flag1=p;
    Ltree[k<<1].w=(Ltree[k<<1].w*Ltree[k].flag1+(Ltree[k<<1].r-Ltree[k<<1].l+1)*Ltree[k].flag)%p;
    Ltree[k<<1|1].w=(Ltree[k<<1|1].w*Ltree[k].flag1+(Ltree[k<<1|1].r-Ltree[k<<1|1].l+1)*Ltree[k].flag)%p;
    Ltree[k].flag=0;
    Ltree[k].flag1=1;
}
void updates(ll left,ll right,ll v,ll k,int com){
	if(Ltree[k].l>right||Ltree[k].r<left) return;
	if(Ltree[k].l>=left&&Ltree[k].r<=right){
		if(com==2){
			Ltree[k].w+=(Ltree[k].r-Ltree[k].l+1)*v;
			Ltree[k].flag=(Ltree[k].flag+v)%p;
		}
		else{
			Ltree[k].w*=v;
			Ltree[k].flag1=Ltree[k].flag1*v%p;
			Ltree[k].flag=(Ltree[k].flag*v)%p;//如果在此乘法之前,有加法,那么预先处理该部分	
		}
		Ltree[k].w%=p;
		return;
	}
	if(Ltree[k].flag || Ltree[k].flag1>1) down(k);
	ll m=(Ltree[k].l+Ltree[k].r)>>1;
	if(left>m){
		updates(left,right,v,k<<1|1,com);
	}
	else if(right<=m){
		updates(left,right,v,k<<1,com);
	}
	else{
		updates(left,m,v,k<<1,com);
		updates(m+1,right,v,k<<1|1,com);
	}
	Ltree[k].w=(Ltree[k<<1].w+Ltree[k<<1|1].w)%p;
}
ll asksum(ll left,ll right,ll k){
	if(Ltree[k].l>right||Ltree[k].r<left) return 0;
	if(Ltree[k].l>=left&&Ltree[k].r<=right){
		return Ltree[k].w;
	}
	if(Ltree[k].flag || Ltree[k].flag1>1) down(k);
	ll m=(Ltree[k].l+Ltree[k].r)>>1;
	ll sum1=0,sum2=0;
	if(left>m) sum1=asksum(left,right,k<<1|1);
	else if(right<=m) sum2=asksum(left,right,k<<1);
	else{
		sum1=asksum(left,m,k<<1);
		sum2=asksum(m+1,right,k<<1|1);
	}
	return (sum1+sum2)%p;
}
int main(){
//	freopen("testdata.in","r",stdin); 
//	freopen("1.out","w",stdout); 
	scanf("%lld%lld%lld",&n,&M,&p);
	for(int i=1;i<=n;i++){
		sfl(ar[i]);
	}
	creat(1,n,1);
	for(int i=0;i<M;i++){
		int opt;
		ll x,y;
		scanf("%d%lld%lld",&opt,&x,&y);
		if(opt==1){
			ll v;
			sfl(v);
			updates(x,y,v,1,1);
		}
		else if(opt==3){
			pfl(asksum(x,y,1));
		}
		else{
			ll v;
			sfl(v);
			updates(x,y,v,1,2);
		}
	}
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值