线段树(转)

线段树详解
线段树代码中如何实现?

(0)定义:

[cpp]  view plain  copy 
  
 
  1. #define maxn 100007  //元素总个数  
  2. int Sum[maxn<<2],Add[maxn<<2];//Sum求和,Add为懒惰标记 (开四倍空间)
  3. int A[maxn],n;//存原数组下标[1,n]

(1)建树:

[cpp]  view plain  copy 
  
 
  1. //PushUp函数更新节点信息,这里是求和
  2. void PushUp(int rt){Sum[rt]=Sum[rt<<1]+Sum[rt<<1|1];}  
  3. //Build函数建立线段树
  4. void Build(int l,int r,int rt){ //[l,r]表示当前节点区间,rt表示当前节点的实际存储位置 
  5.     if(l==r) {//若到达叶节点 
  6.         Sum[rt]=A[l];//存储A数组的值
  7.         return;  
  8.     }  
  9.     int m=(l+r)>>1;  
  10.    //左右递归
  11.     Build(l,m,rt<<1);  
  12.     Build(m+1,r,rt<<1|1);  
  13.     //更新信息
  14.     PushUp(rt);  
  15. }  

(2)点修改:

假设A[L]+=C:
[cpp]  view plain  copy 
  
 
  1. void Update(int L,int C,int l,int r,int rt){//[l,r]表示当前区间,rt是当前节点编号//l,r表示当前节点区间,rt表示当前节点编号  
  2.     if(l==r){//到达叶节点,修改叶节点的值
  3.         Sum[rt]+=C;  
  4.         return;  
  5.     }  
  6.     int m=(l+r)>>1;  
  7.    //根据条件判断往左子树调用还是往右
  8.     if(L <= m) Update(L,C,l,m,rt<<1);  
  9.     else       Update(L,C,m+1,r,rt<<1|1);  
  10.     PushUp(rt);//子节点的信息更新了,所以本节点也要更新信息
  11. }   

点修改其实可以写的更简单,只需要把一路经过的Sum都+=C就行了,不过上面的代码更加规范,在题目更加复杂的时候,按照格式写更不容易错。



(3)区间修改:

假设A[L,R]+=C

首先是下推标记的函数:
[cpp]  view plain  copy
  1. void PushDown(int rt,int ln,int rn){  
  2.     //ln,rn为左子树,右子树的数字数量。   
  3.     if(Add[rt]){  
  4.         //下推标记   
  5.         Add[rt<<1]+=Add[rt];  
  6.         Add[rt<<1|1]+=Add[rt];  
  7.         //修改子节点的Sum使之与对应的Add相对应   
  8.         Sum[rt<<1]+=Add[rt]*ln;  
  9.         Sum[rt<<1|1]+=Add[rt]*rn;  
  10.         //清除本节点标记   
  11.         Add[rt]=0;  
  12.     }  
  13. }  

更新函数:

[cpp]  view plain  copy
  1. void Update(int L,int R,int C,int l,int r,int rt){//L,R表示操作区间,l,r表示当前节点区间,rt表示当前节点编号   
  2.     if(L <= l && r <= R){//如果本区间完全在操作区间[L,R]以内   
  3.         Sum[rt]+=C*(r-l+1);//更新数字和,向上保持正确  
  4.         Add[rt]+=C;//增加Add标记,表示本区间的Sum正确,子区间的Sum仍需要根据Add的值来调整  
  5.         return ;   
  6.     }  
  7.     int m=(l+r)>>1;  
  8.     PushDown(rt,m-l+1,r-m);//下推标记  
  9.     //这里判断左右子树跟[L,R]有无交集,有交集才递归   
  10.     if(L <= m) Update(L,R,C,l,m,rt<<1);  
  11.     if(R >  m) Update(L,R,C,m+1,r,rt<<1|1);   
  12.     PushUp(rt);//更新本节点信息   
  13. }   


(4)区间查询(以求和为例):

询问A[L..R]的和
注意到,整个函数的递归过程中,L,R是不变的。
首先如果当前区间[l,r]在[L,R]内部,就直接累加答案
如果左子区间与[L,R]有重叠,就递归左子树,右子树同理。
[cpp]  view plain  copy 
  
 
  1. int Query(int L,int R,int l,int r,int rt){//[L,R]表示操作区间,[l,r]表示当前区间,rt:当前节点编号
  2.     if(L <= l && r <= R){  
  3.        //在区间内直接返回
  4.         return Sum[rt];  
  5.     }  
  6.     int m=(l+r)>>1;  
  7.     //左子区间:[l,m] 右子区间:[m+1,r]  求和区间:[L,R]
  8.     //下推标记,因为之前下面有些结点的值没有被修改(只有点修改的话,这句不需要)
  9.     PushDown(rt,m-l+1,r-m);
  10.    //累加答案
  11.     int ANS=0;  
  12.     if(L <= m) ANS+=Query(L,R,l,m,rt<<1);//左子区间与[L,R]有重叠,递归
  13.     if(R >  m) ANS+=Query(L,R,m+1,r,rt<<1|1); //右子区间与[L,R]有重叠,递归
  14.     return ANS;  
  15. }   

把上面代码综合一下:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>

#define maxn 10000

using namespace std;

int sum[maxn<<2],add[maxn<<2];
int a[maxn],n;

void pushup(int rt) //更新节点 
{
	sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}

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

void pushdown(int rt,int ln,int rn)  //下推标记 
{  //ln,rn为左子树,右子树的数字数量
	if(add[rt])
	{
		add[rt<<1]+=add[rt];
		add[rt<<1|1]+=add[rt];
		sum[rt<<1]+=add[rt]*ln;
		sum[rt<<1|1]+=add[rt]*rn;
		add[rt]=0;
	}
}

void update(int L,int R,int c,int l,int r,int rt)   //L,R表示操作区间,l,r表示当前节点区间,rt表示当前节点编号   
{
	if(L<=l&&r<=R)
	{
		sum[rt]+=c*(r-l+1);
		add[rt]+=c;
		return ;
	}
	int m=(l+r)>>1;
	pushdown(rt,m-l+1,r-m);
	if(L<=m)
		update(L,R,c,l,m,rt<<1);
	if(m<R)
		update(L,R,c,m+1,r,rt<<1|1);
	pushup(rt);
}

int query(int L,int R,int l,int r,int rt)
{
	if(L<=l&&r<=R)
		return sum[rt];
	int m=(l+r)>>1;
	pushdown(rt,m-l+1,r-m);
	int ans=0;
	if(L<=m)
		ans+=query(L,R,l,m,rt<<1);
	if(m<R)
		ans+=query(L,R,m+1,r,rt<<1|1);
	return ans;
}

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	memset(sum,0,sizeof(sum));
	memset(add,0,sizeof(add));
	build(1,n,1);
	printf("%d\n",query(2,3,1,n,1));
	update(2,4,4,1,n,1);
	printf("%d",query(1,4,1,n,1));
	return 0;
} 


区间连续0问题:

  1. #define maxn 200001  
  2. using namespace std;  
  3. int L[maxn<<2][2];//从左开始连续零个数   
  4. int R[maxn<<2][2];//从右   
  5. int Max[maxn<<2][2];//区间最大连续零   
  6. bool Pure[maxn<<2][2];//是否全零   
  7. int M[2];  
  8. void PushUp(int rt,int k){//更新rt节点的四个数据  k来选择两棵线段树  
  9.     Pure[rt][k]=Pure[rt<<1][k]&&Pure[rt<<1|1][k];   
  10.     Max[rt][k]=max(R[rt<<1][k]+L[rt<<1|1][k],max(Max[rt<<1][k],Max[rt<<1|1][k]));  
  11.     L[rt][k]=Pure[rt<<1][k]?L[rt<<1][k]+L[rt<<1|1][k]:L[rt<<1][k];  
  12.     R[rt][k]=Pure[rt<<1|1][k]?R[rt<<1|1][k]+R[rt<<1][k]:R[rt<<1|1][k];  
  13. }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值