codevs线段树练习5(双重标记)

有n个数和5种操作

add a b c:把区间[a,b]内的所有数都增加c

set a b c:把区间[a,b]内的所有数都设为c

sum a b:查询区间[a,b]的区间和

max a b:查询区间[a,b]的最大值

min a,b:查询区间[a,b]的最小值

双标记,注意双标记间的关系即可,注意一下,不是很难,顺便练代码能力,可惜一次编完,还是有两个地方没考虑到

对于线段树add的部分,以后一定要再注意再注意!add可能会多次,所以要用+=

a【i】.add+=add;!!!!

set用一个bool来判断是否被区间赋值,更方便,且不会有bug。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<cstdlib>
using namespace std;
typedef long long ll;
const ll inf=100000000000ll;//pay attention to 'll'

int n,m;

struct aa
{
	ll mx,mi,sum,add;
	int l,r,set;
}a[400005+1000];

void up(int i)
{
	a[i].mi=inf;
	a[i].mx=-inf;
	a[i].sum=0;
	
	if (a[i<<1].l)
	{
		a[i].mi=min(a[i].mi,a[i<<1].mi);
		a[i].mx=max(a[i].mx,a[i<<1].mx);
		a[i].sum+=a[i<<1].sum;
	}
	if (a[i<<1|1].l)
	{
		a[i].mi=min(a[i].mi,a[i<<1|1].mi);
		a[i].mx=max(a[i].mx,a[i<<1|1].mx);
		a[i].sum+=a[i<<1|1].sum;
	}
}

void down(int i)
{
	if (a[i].set)
	{
		a[i<<1].set=a[i<<1|1].set=1;
		a[i<<1].add=a[i<<1|1].add=0;
		ll st=a[i].mi;
		
		a[i<<1].sum=st*(a[i<<1].r-a[i<<1].l+1);
		a[i<<1|1].sum=st*(a[i<<1|1].r-a[i<<1|1].l+1);
		
		a[i<<1].mi=a[i<<1|1].mi=
		a[i<<1].mx=a[i<<1|1].mx=st;
		
		a[i].set=0;
	}
	
	if (a[i].add)
	{		
		ll ad=a[i].add;
		
		a[i<<1].mi+=ad;
		a[i<<1].mx+=ad;
		a[i<<1].sum+=(a[i<<1].r-a[i<<1].l+1)*ad;
		if (a[i<<1].set==0) a[i<<1].add+=ad; 
		
		
		a[i<<1|1].mi+=ad;
		a[i<<1|1].mx+=ad;
		a[i<<1|1].sum+=(a[i<<1|1].r-a[i<<1|1].l+1)*ad;
		if (a[i<<1|1].set==0) a[i<<1|1].add+=ad;
		//这个地方一定要用  += ,因为可能重复加
		//这个错误犯了好多次了!!,要非常注意 
		a[i].add=0;
	}
	
}

void build(int i,int l,int r)
{
	a[i].l=l;a[i].r=r;
	if (l==r)
	{
		ll x;
		scanf("%lld",&x);
		a[i].mx=a[i].mi=a[i].sum=x;
		return ;
	}
	int mid=(l+r)>>1;
	build(i<<1,l,mid);
	build(i<<1|1,mid+1,r);
	up(i);
}

void add(int i,int l,int r,ll ad)
{
	if (a[i].l==l&&a[i].r==r)
	{	
		a[i].sum+=(a[i].r-a[i].l+1)*ad;
		a[i].mx+=ad;
		a[i].mi+=ad;
		if (a[i].set) return ;
				
		a[i].add+=ad;//这个地方一定要用  += ,因为可能重复加
		
		return ;
	}
	down(i);
	int mid=(a[i].l+a[i].r)>>1;
	if (mid>=r) add(i<<1,l,r,ad);
	else if (mid<l) add(i<<1|1,l,r,ad);
	else add(i<<1,l,mid,ad),add(i<<1|1,mid+1,r,ad);
	up(i);
}

void set(int i,int l,int r,ll st)
{
	if (a[i].l==l&&a[i].r==r)
	{
		a[i].set=1;
		a[i].sum=st*(a[i].r-a[i].l+1);
		a[i].mx=a[i].mi=st;
		a[i].add=0;
		
		return ;
	}
	down(i);
	int mid=(a[i].l+a[i].r)>>1;
	if (mid>=r) set(i<<1,l,r,st);
	else if (mid<l) set(i<<1|1,l,r,st);
	else set(i<<1,l,mid,st),set(i<<1|1,mid+1,r,st);
	up(i);
}

ll sum(int i,int l,int r)
{
	if (a[i].l==l&&a[i].r==r) return a[i].sum;
	down(i);
	int mid=(a[i].l+a[i].r)>>1;
	if (mid>=r) return sum(i<<1,l,r);
	else if (mid<l) return sum(i<<1|1,l,r);
	return sum(i<<1,l,mid)+sum(i<<1|1,mid+1,r);
}

ll findmx(int i,int l,int r)
{
	if (a[i].l==l&&a[i].r==r) return a[i].mx;
	down(i);
	int mid=(a[i].l+a[i].r)>>1;
	if (mid>=r) return findmx(i<<1,l,r);
	else if (mid<l) return findmx(i<<1|1,l,r);
	return max(findmx(i<<1,l,mid),findmx(i<<1|1,mid+1,r));
}

ll findmi(int i,int l,int r)
{
	if (a[i].l==l&&a[i].r==r) return a[i].mi;
	down(i);
	int mid=(a[i].l+a[i].r)>>1;
	if (mid>=r) return findmi(i<<1,l,r);
	else if (mid<l) return findmi(i<<1|1,l,r);
	return min(findmi(i<<1,l,mid),findmi(i<<1|1,mid+1,r));
}

int main()
{
	scanf("%d%d",&n,&m);
	build(1,1,n);
	char s[10];
	int x,y;
	ll z;
	while (m--)
	{
		scanf("%s",s);scanf("%d%d",&x,&y);
		switch(s[1])
		{
			case 'd':scanf("%lld",&z);add(1,x,y,z);break;//add
			case 'e':scanf("%lld",&z);set(1,x,y,z);break;//set
			case 'u':printf("%lld\n",sum(1,x,y));break;//sum
			case 'a':printf("%lld\n",findmx(1,x,y));break;//max
			case 'i':printf("%lld\n",findmi(1,x,y));break;//min
		}
	}
	
	return 0;
}


总结

1:关于线段树,splay,lct等等的数据结构中,太多都要用到up和down函数,关于down的顺序其实是需要注意的。

首先我们在打标记的时候,同时就要更新标记本身这个节点的答案(such as sum【i】,mx【i】)并不是要等到,push_down的时候再处理本身这个节点的,push_down的时候只处理他的孩子节点(的sum,mx……),跟它本身节点的值已经没关系了,他本身节点的值已经更新过了。


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值