P3372 线段树1

P3372 线段树1

题目分析

关于 p u s h d o w n pushdown pushdown操作,是懒标记( t a g tag tag)下放和子节点信息更新的操作,其目的是维护线段树的父节点与子节点之间的关系,在每次更新( u p d a t e update update)和查询( q u e r y query query)子节点操作前都要 p u s h d o w n pushdown pushdown,以保证更新和查询子节点时子节点的值是正确的. 否则,所有的信息都会记录在根节点,导致子节点无法被更新.

同理我们有 p u s h u p pushup pushup操作,是子节点更新完毕后回溯更新父节点的过程. 与 p u s h d o w n pushdown pushdown不同的是,此时要更新的只有父节点的值 a n s ans ans而没有 t a g tag tag.

void update(ll p,ll l,ll r,ll ql,ll qr,ll k){
	if(ql<=l&&r<=qr){
		ans[p]+=(r-l+1)*k;
		tag[p]+=k;
		return ;
	}
	ll mid=(l+r)>>1;
	push_down(p,l,r);
	if(ql<=mid)update(ls(p),l,mid,ql,qr,k);
	if(mid<qr)update(rs(p),mid+1,r,ql,qr,k);
	ans[p]=ans[ls(p)]+ans[rs(p)];//更新父节点
}

注意 u p d a t e update update中的 p u s h d o w n pushdown pushdown操作,它维护了子节点的信息正确性.

我不会告诉你我就是因为一开始没有它板子才10分的

程序实现

#include<bits/stdc++.h>
#define ll long long
#define maxn 100010
using namespace std;
int n,m;
ll a[maxn],ans[maxn<<2],tag[maxn<<2];
ll ls(ll x){return x<<1;}
ll rs(ll x){return x<<1|1;}
void build(ll p,ll l,ll r){
	if(l==r){ans[p]=a[l];return ;}
	ll mid=(l+r)>>1;
	build(ls(p),l,mid);
	build(rs(p),mid+1,r);
	ans[p]=ans[ls(p)]+ans[rs(p)];//pushup操作,更新父节点
}
void push_down(ll p,ll l,ll r){
	ll mid=(l+r)>>1;
	ans[ls(p)]+=tag[p]*(mid-l+1);
	ans[rs(p)]+=tag[p]*(r-mid);
	tag[ls(p)]+=tag[p];
	tag[rs(p)]+=tag[p];//以上更新子节点信息
	tag[p]=0;//懒标记全部下放
}
void update(ll p,ll l,ll r,ll ql,ll qr,ll k){
	if(ql<=l&&r<=qr){
		ans[p]+=(r-l+1)*k;
		tag[p]+=k;
		return ;
	}
	ll mid=(l+r)>>1;
	push_down(p,l,r);
	if(ql<=mid)update(ls(p),l,mid,ql,qr,k);
	if(mid<qr)update(rs(p),mid+1,r,ql,qr,k);
	ans[p]=ans[ls(p)]+ans[rs(p)];//更新父节点
}
ll query(ll p,ll l,ll r,ll ql,ll qr){
	if(ql<=l&&r<=qr){return ans[p];}
	ll ret=0;
	ll mid=(l+r)>>1;
	push_down(p,l,r);
	if(ql<=mid)ret+=query(ls(p),l,mid,ql,qr);
	if(mid<qr)ret+=query(rs(p),mid+1,r,ql,qr);
	return ret;
}
int main(){
	ll a1,b,c,d;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%lld",&a[i]);
	}
	build(1,1,n);
	for(int i=1;i<=m;i++){
		scanf("%lld",&a1);
		if(a1==1){
			scanf("%lld%lld%lld",&b,&c,&d);
			update(1,1,n,b,c,d);
		}
		else {
			scanf("%lld%lld",&b,&c);
			printf("%lld\n",query(1,1,n,b,c));
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值