线段树

线段树是一种高效的数据结构,能处理区间查询与更新操作。本文介绍了线段树的基本概念、模板代码以及一个实际问题的应用示例,展示了如何通过递归实现线段树,并利用惰性更新优化区间更新的时间复杂度,实现对大规模数据的快速处理。
摘要由CSDN通过智能技术生成

线段树

  相比于树状数组只能解决计算前缀和的问题,线段树则更为通用,我们可以用线段树实现树状数组所有的功能,但是线段树实现较为复杂,通常可以直接套用模板,再针对具体问题做一些变化,这里不再赘述;
  线段树利用冗余存储的方式来加快搜索速度,这是时间与空间折中的经典思想;线段树的形状是一颗完全二叉树,其深度为log2(n)取上整,算下来所有线段树中的节点数不会超会数据个数的4倍,这样我们就可以在初始化时直接设置为原始数据数目的4倍即可;
  下面给出一个模板以及例题:

线段树模板

  相对而言递归实现更容易理解,这里也只给出递归实现;

int n;
long long arr[400001];
long long lazy[400001];
long long num[100001];
void up(int rt){//更新 
	arr[rt]=arr[rt<<1]+arr[rt<<1|1];
	return;
}
//初始化建立线段树 
void build(int l,int r,int rt){//原始数据区间左端,区间右端,线段树中保存原始数据中这一段区间的节点编号
	 if(l==r){
	 	arr[rt]=num[l];
	 	return;
	 } 
	 int m=(l+r)/2;
	 build(l,m,rt<<1);
	 build(m+1,r,rt<<1|1);
	 up(rt);
	 return;
} 
void down(int ln,int rn,int rt){
	//延迟更新
	//为了避免区间更新时间复杂度退化为O(n),这里使用惰性更新的原则
	//也即用到时才会更新,不用时就不更新,值更新到使用点为止
	//更新线段树中左右节点的值 
	arr[rt<<1]+=ln*lazy[rt];
	arr[rt<<1|1]+=rn*lazy[rt];
	//更新左右节点中惰性标记的值 
	lazy[rt<<1]+=lazy[rt];
	lazy[rt<<1|1]+=lazy[rt];
	lazy[rt]=0;
}
//点更新 
void add(int x,long long v,int l,int r,int rt){ 
	if(l==r){
		arr[rt]+=v;
		return;
	}
	int m=(l+r)/2;
	down(m-l+1,r-m,rt);//同时有点更新与区间更新时,这里要加延迟更新的操作
	if(x<=m)add(x,v,l,m,rt<<1);
	else add(x,v,m+1,r,rt<<1|1);
	up(rt);
	return;
}
//区间更新 
void update(int x,int y,long long v,int l,int r,int rt){
	if(x<=l&&y>=r){
		arr[rt]+=(r-l+1)*v;
		lazy[rt]+=v;
		return;
	}
	int m=(l+r)/2;
	down(m-l+1,r-m,rt);
	if(x<=m)update(x,y,v,l,m,rt<<1);
	if(y>m)update(x,y,v,m+1,r,rt<<1|1);
	up(rt);
	return; 
} 
//区间查询 
long long query(int x,int y,int l,int r,int rt){
	if(x<=l&&y>=r){
		return arr[rt];
	}
	int m=(l+r)/2;
	down(m-l+1,r-m,rt);
	long long ans=0;
	if(x<=m)ans+=query(x,y,l,m,rt<<1);
	if(y>m)ans+=query(x,y,m+1,r,rt<<1|1);
	return ans;
}

例题

一个简单的整数问题

#include<bits/stdc++.h>
using namespace std;

int n;
long long arr[400001];
long long lazy[400001];
long long num[100001];
void up(int rt){//更新 
	arr[rt]=arr[rt<<1]+arr[rt<<1|1];
	return;
}
//初始化建立线段树 
void build(int l,int r,int rt){//原始数据区间左端,区间右端,线段树中保存原始数据中这一段区间的节点编号
	 if(l==r){
	 	arr[rt]=num[l];
	 	return;
	 } 
	 int m=(l+r)/2;
	 build(l,m,rt<<1);
	 build(m+1,r,rt<<1|1);
	 up(rt);
	 return;
} 
void down(int ln,int rn,int rt){
	//延迟更新
	//为了避免区间更新时间复杂度退化为O(n),这里使用惰性更新的原则
	//也即用到时才会更新,不用时就不更新,值更新到使用点为止
	//更新线段树中左右节点的值 
	arr[rt<<1]+=ln*lazy[rt];
	arr[rt<<1|1]+=rn*lazy[rt];
	//更新左右节点中惰性标记的值 
	lazy[rt<<1]+=lazy[rt];
	lazy[rt<<1|1]+=lazy[rt];
	lazy[rt]=0;
}
//点更新 
void add(int x,long long v,int l,int r,int rt){ 
	if(l==r){
		arr[rt]+=v;
		return;
	}
	int m=(l+r)/2;
	down(m-l+1,r-m,rt);//同时有点更新与区间更新时,这里要加延迟更新的操作
	if(x<=m)add(x,v,l,m,rt<<1);
	else add(x,v,m+1,r,rt<<1|1);
	up(rt);
	return;
}
//区间更新 
void update(int x,int y,long long v,int l,int r,int rt){
	if(x<=l&&y>=r){
		arr[rt]+=(r-l+1)*v;
		lazy[rt]+=v;
		return;
	}
	int m=(l+r)/2;
	down(m-l+1,r-m,rt);
	if(x<=m)update(x,y,v,l,m,rt<<1);
	if(y>m)update(x,y,v,m+1,r,rt<<1|1);
	up(rt);
	return; 
} 
//区间查询 
long long query(int x,int y,int l,int r,int rt){
	if(x<=l&&y>=r){
		return arr[rt];
	}
	int m=(l+r)/2;
	down(m-l+1,r-m,rt);
	long long ans=0;
	if(x<=m)ans+=query(x,y,l,m,rt<<1);
	if(y>m)ans+=query(x,y,m+1,r,rt<<1|1);
	return ans;
}

int main(){
    //初始化lazy数组
    memset(lazy,0,sizeof(lazy));
	int m;
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%lld",&num[i]);
	}
	build(1,n,1);
	for(int i=0;i<m;i++){
		char str[2];
		scanf("%s",&str);
		if(str[0]=='Q'){
			int a;
			scanf("%d",&a);
			printf("%lld \n",query(a,a,1,n,1));
		}else{
			int a,b;
			long long v;
			scanf("%d %d %lld",&a,&b,&v);
			update(a,b,v,1,n,1);
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值