线段树(由浅入深),详解!!

线段树详解

线段树,顾名思义,由线段构成的树;非常实用的数据结构;
线段树由浅入深分为三各层次
1.单点修改+区间查询
2.区间修改+单点查询
3.区间修改+区间查询

1单点修改+区间查询

先上问题:
codvs1080
读完题可能大家有很多方法,但面对庞大的数据,会发现都略有欠缺,所以就轮到线段树上场了;

假设数列为1 2 3 4 5 6 7 8
那么线段树就是这样的
每个节点维护某个区间的左端点l和右端点r,因为题目要求维护区间和,那么节点信息再加一条该区间的和s;

那么一刻整整齐齐的线段树就完成了;

对于题目,对该线段树有两个操作
1.某个数增加后一些包含此数的区间和也要改变(即节点的s)
2.查询某段区间的和;

逐个击破
1.假设第x个数增加k,那么从根节点开始,判断该节点维护的区间包不包含第x个数,若包含,s+=k,再依次遍历左右儿子;若某个区间不包含x,那么不必继续,继续往下更不可能包括;

2.查询分三种情况:
a.该节点区间与查询区间完全分离,return 0;
b.节点区间是查询区间的子集,return s;
c.节点区间与查询区间部分相交,return lson(左儿子)的返回值+rson的返回值;

说了那么多直接上代码吧

```#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<vector>
#define MAXN 100010
using namespace std;
struct Node{int l,r,ls,rs,s;};
vector<Node> N; 
int n,m,a[MAXN];
void build_tree(int v){//建树
	if(N[v].l==N[v].r){N[v].s=a[N[v].l];return;}
	int mid=(N[v].l+N[v].r)>>1;//二分区间
	N.push_back((Node){N[v].l,mid,0,0,0}); N[v].ls=N.size()-1;//生成左儿子
	N.push_back((Node){mid+1,N[v].r,0,0,0}); N[v].rs=N.size()-1;//生成右儿子
	build_tree(N[v].ls); build_tree(N[v].rs);
	N[v].s=N[N[v].ls].s+N[N[v].rs].s;//和为左儿子+右儿子区间和
}
void Change(int v,int addnode,int val){
	if(N[v].l<=addnode&&N[v].r>=addnode){//包含在区间中
		N[v].s+=val;
		if(N[v].l==N[v].r) return;
		Change(N[v].ls,addnode,val);
		Change(N[v].rs,addnode,val);
	}
}
int Query(int v,int l,int r){
	if(N[v].l>=l&&N[v].r<=r) return N[v].s;//包含
	else if(N[v].l>r||N[v].r<l) return 0;//分离
	else return Query(N[v].ls,l,r)+Query(N[v].rs,l,r);//相交
}
int main(){
	scanf("%d",&n);
	N.push_back((Node){1,n,0,0,0});
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	build_tree(0);
//	for(int i=1;i<=n;i++) cout<<a[i]<<endl;
	scanf("%d",&m);
	while(m--){
		int type,a,b; scanf("%d%d%d",&type,&a,&b);
		if(type==1) Change(0,a,b);
		if(type==2) printf("%d\n",Query(0,a,b));
	}
	return 0;
}

区间修改单点查询

codvs1081

区间修改就不那么好办了,把区间每个数循环加上k吗?那复杂度又炸了;下面上王牌—lazy tag----懒惰标记,也是顾名思义,我才懒得加数呢
,我记着那些区间要加k,哪些不加,等你问的时候我再算,方便了许多,也提高的很多效率。

所以每个节点需要维护新的信息,N[v].d,表示该节点维护的区间每个数需加上d.

那么进行操作2时,若N[v].l>=l&&N[v].r<=r即被包含那么N[v].d+=k
那么有一个问题,假如某操作将1–8加上k,我们就将节点1打上懒惰标记,可是1的子节点区间为1–4的和5—8也应加上怎么办呢; so easy,询问的时候讲祖先的懒惰标记一层层累加下来就行了;

代码与之前差不多

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<vector>
#include<iterator>
#define MAXN 100010
using namespace std;
struct Node{int l,r,ls,rs,s;};
vector<Node> N; 
int n,m,a[MAXN];
void build_tree(int v){
	if(N[v].l==N[v].r) return; 
	int mid=(N[v].l+N[v].r)>>1;
	N.push_back((Node){N[v].l,mid,0,0,0}); N[v].ls=N.size()-1;
	N.push_back((Node){mid+1,N[v].r,0,0,0}); N[v].rs=N.size()-1;
	build_tree(N[v].ls); build_tree(N[v].rs);
	//因为是单点查询就没必要把区间和求出来了
}
void Change(int v,int l,int r,int val){
	if(N[v].l>r||N[v].r<l) return;//分离
	else if(N[v].l>=l&&N[v].r<=r) N[v].s+=val;//包含
	else{//相交
		Change(N[v].ls,l,r,val);
		Change(N[v].rs,l,r,val);
	}
}
void Query(int v,int pos,int sum){//sum记录累加下来的懒惰标记
	if(N[v].l>pos||N[v].r<pos) return;//分离
	if(N[v].l==N[v].r){//找到符合要求的叶子节点
		printf("%d\n",sum+a[N[v].l]+N[v].s);
		return;
	}
	Query(N[v].ls,pos,sum+N[v].s);//寻找叶子节点,因为是单点查询
	Query(N[v].rs,pos,sum+N[v].s);
}
int main(){
	scanf("%d",&n);
	N.push_back((Node){1,n,0,0,0});
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	build_tree(0);
	scanf("%d",&m);
	while(m--){
		int type; scanf("%d",&type);
		if(type==1){
			int a,b,c; scanf("%d%d%d",&a,&b,&c);
			Change(0,a,b,c);
		//	for(int i=0;i<N.size();i++) cout<<i<<' '<<N[i].l<<' '<<N[i].r<<' '<<N[i].ls<<' '<<N[i].rs<<' '<<N[i].s<<endl;
		}
		if(type==2){
			int x; scanf("%d",&x);
			Query(0,x,0);
		}
	}
	return 0;
}

3.区间修改+区间查询

codvs1082
区间查询就无法把祖先的懒惰标记累加下来了,因为给的查询区间有可能在线段树上分为许多块,这是很难处理的。
那么就在原基础上加入push_down操作,将当前节点的懒惰标记推给自己的儿子,并把自己的s更新
如下

void push_down(int v){
	N[v].s=N[v].s+N[v].d*(LL)(N[v].r-N[v].l+1);
	if(N[v].l<N[v].r) N[N[v].ls].d+=N[v].d , N[N[v].rs].d+=N[v].d;//若有儿子
	N[v].d=0;//该节点懒惰标记清零
}

这样维护了每个区间正确的s和d,剩下的操作就与上面类似了
直接上代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<vector>
#include<iterator>
#define LL long long
#define MAXN 200010
using namespace std;
struct Node{int l,r,ls,rs;LL s,d;};
vector<Node> N; 
int n,m,a[MAXN];
void push_down(int v){
	N[v].s=N[v].s+N[v].d*(LL)(N[v].r-N[v].l+1);
	if(N[v].l<N[v].r) N[N[v].ls].d+=N[v].d , N[N[v].rs].d+=N[v].d;
	N[v].d=0;
}
void build_tree(int v){
	if(N[v].l==N[v].r){N[v].s=a[N[v].l];return;} 
	int mid=(N[v].l+N[v].r)>>1;
	N.push_back((Node){N[v].l,mid,0,0,0}); N[v].ls=N.size()-1;
	N.push_back((Node){mid+1,N[v].r,0,0,0}); N[v].rs=N.size()-1;
	build_tree(N[v].ls); build_tree(N[v].rs);
	N[v].s=N[N[v].ls].s+N[N[v].rs].s;
}
void Change(int v,int l,int r,int val){
	push_down(v);//由于在上一层需要用到正确的v节点的信息,所以在避免下行的返回导致信息丢失先更新信息
	if(N[v].l>r||N[v].r<l) return;
	if(N[v].l>=l&&N[v].r<=r){
		N[v].d+=val;
		push_down(v);
	}
	else{
		Change(N[v].ls,l,r,val);
		Change(N[v].rs,l,r,val);
		N[v].s=N[N[v].ls].s+N[N[v].rs].s;//
	}
}
LL Query(int v,int l,int r){
	if(N[v].l>r||N[v].r<l) return 0;
	push_down(v);
	if(N[v].l>=l&&N[v].r<=r) return N[v].s;
	return Query(N[v].ls,l,r)+Query(N[v].rs,l,r);
}
int main(){
	scanf("%d",&n);
	N.push_back((Node){1,n,0,0,0});
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	build_tree(0);
	scanf("%d",&m);
	while(m--){
		int type; scanf("%d",&type);
		if(type==1){
			int a,b,c; scanf("%d%d%d",&a,&b,&c);
			Change(0,a,b,c);
		}
		if(type==2){
			int x,y; scanf("%d%d",&x,&y);
			printf("%lld\n",Query(0,x,y));
		}
	}
	return 0;
}

就这么多啦!!谢谢!!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值