高级数据结构——线段树

听作者讲骚话

今天是很沉重的一天,进来CCF官宣取消OI,也不知道是不是真的。
OIer们不要放弃梦想!大家一定要勇往直前!
这线段树会很难…至少我是这么想的…不过我也是经过百般实验才敢写这个博客!

线段树我要你何用?

假定我给你一个定区间,让你以最快的速度做这样的操作:修改其中的一个子区间,查询另一个子区间的值,而且重复这些操作。你会说:for循环。没错,这复杂度是O(N)线段树可以说是区间神器,它拥有四个处理区间的基本功能:单点查询,单点修改,区间查询,区间修改。除了建树需要O(NlogN)的复杂度以外,其余操作的复杂度是O(logN)。
先给一个线段树的图吧。
在这里插入图片描述
这里的数字是点标,不是点值。
详细解析后续更新。
代码里提供微解析。
完整代码如下:

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
#define maxm 10001
int n,a[maxm],sum[maxm],delta[maxm];

void update(int x){sum[x]=sum[x<<1]+sum[x<<1|1];return;}
//这里是统计左右两个子节点的值并付给自己,每一次值更新都要update一次
//x<<1|1等价于x*2+1,位运算有什么疑问可以去看下面的连接
//https://blog.csdn.net/Martisum/article/details/96182646

//此函数用于标记下放
void pushdown(int now,int l,int r){
	int mid=(l+r)>>1;
	if(delta[now]!=0){
		sum[now<<1]+=delta[now]*(mid-l+1);
		//每个右节点包含的所有叶子结点都需要被更新,所以得乘以叶子结点的数目
		delta[now<<1]+=delta[now];
		//当然,右节点也需要带上懒惰标记
		//左节点重复操作
		sum[now<<1|1]+=delta[now]*(r-mid);
		delta[now<<1|1]+=delta[now];
		//最后标记下放完毕,源节点的懒惰标记清零
		delta[now]=0;
	}
	return;
}

//递归建树
void build(int now,int l,int r){
	if(l==r){sum[now]=a[l];return;}
	int mid=(l+r)>>1;
	//递归处理左边和右边
	build(now<<1,l,mid);
	build(now<<1|1,mid+1,r);
	//处理完毕(分到最细)然后逐步统计上去
	update(now);
	return;
}

//这个是单点修改
void changepoint(int now,int l,int r,int x,int v){
	//如果l==r说明这个值目标结点(因为你要找的结点必须得在区间内,而你又是严格按左右原则找,所以必然是)
	if(l==r){sum[now]+=v;return;}
	int mid=(l+r)>>1;
	//记得下放懒惰标记
	pushdown(now,l,r);
	//在左边(mid的左边)就找左边,反之找右边
	if(x<=mid){changepoint(now<<1,l,mid,x,v);}
	else{changepoint(now<<1|1,mid+1,r,x,v);}
	//老规矩[手动滑稽],你改了你父亲也要改,所以update
	update(now);
	return;
}

//这是单点查询
int askpoint(int now,int l,int r,int x){
	//如果l==r说明这个值目标结点(因为你要找的结点必须得在区间内,而你又是严格按左右原则找,所以必然是)
	if(l==r){return sum[now];}
	int mid=(l+r)>>1;
	pushdown(now,l,r);
	//简直跟单点修改的过程一样对吧
	if(x<=mid){return askpoint(now<<1,l,mid,x);}
	else{return askpoint(now<<1|1,mid+1,r,x);}
	//你看这里查询就不需要update
}

int askrange(int now,int l,int r,int tl,int tr){
	//如果当前控制区间被目标区间包含,直接返回当前区间值
	if(tl<=l&&r<=tr) return sum[now];
	int mid=(l+r)>>1,ans=0;
	pushdown(now,l,r);
	//否则就往左和右寻找,吧每一级递归的结果交给ans,每一层递归统计上去
	if(tl<=mid){ans+=askrange(now<<1,l,mid,tl,tr);}
	if(tr>mid){ans+=askrange(now<<1|1,mid+1,r,tl,tr);}
	return ans;
}

void changerange(int now,int l,int r,int tl,int tr,int v){
	//如果被包含就直接生成懒人标记并进行区间赋值
	//v代表的是给这一个区间内每一个叶子加上v!!!!!!不是给这个区间
	if(tl<=l&&r<=tr){sum[now]+=(r-l+1)*v;delta[now]+=v;return;}
	int mid=(l+r)>>1;
	pushdown(now,l,r);
	//处理左边和右边
	if(tl<=mid){changerange(now<<1,l,mid,tl,tr,v);}
	if(tr>mid){changerange(now<<1|1,mid+1,r,tl,tr,v);}
	update(now);
	return;
}

int main(){
	std::ios::sync_with_stdio(false);
	freopen("tin.txt","r",stdin);
	cin>>n;
	for(int i=1;i<=n;i++){cin>>a[i];}
	build(1,1,n);
	
	freopen("CON","r",stdin);
	int k,b,c;
	char ju;
	LOOP:cout<<"你要做什么"<<endl;
	cout<<"1.区间修改"<<endl<<"2.区间查询"<<endl<<"3.单点修改"<<endl<<"4.单点查询"<<endl<<"5.退出"<<endl;
	cin>>ju;
	if(ju=='1'){
		cout<<"输入l,r,v";
		cin>>k>>b>>c;cout<<endl;
		changerange(1,1,n,k,b,c);
	}else if(ju=='2'){
		cout<<"输入l,r";
		cin>>k>>b;cout<<endl;
		cout<<"结果是:"<<askrange(1,1,n,k,b)<<endl;
	}else if(ju=='3'){
		cout<<"输入p,v";
		cin>>k>>b;
		changepoint(1,1,n,k,b);
		cout<<endl;
	}else if(ju=='4'){
		cout<<"输入p";
		cin>>k;
		cout<<"结果是:"<<askpoint(1,1,n,k)<<endl;
	}else if(ju=='5'){goto FINA;}
	goto LOOP;
	FINA:cout<<"成功退出"<<endl;
	
	return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值