【数据结构】 线段树

……线段树可是一个高端的东西。


一、啥是线段树

信息迅速发端的现在,人们已经发明出一个又一个算法以及数据结构。当人们嫌人眼校对会有错误,又会引起眼神疲劳时,人们发明了KMP;当人们嫌前缀和麻烦,效率不够,不能修改时,人们想到了树状数组;当人么又嫌树状数组只能单点修改不能区间修改时,人们,人们,人们,又想到了————

线 线段树

没错,线段树就是一个支持区间修改和区间查询的一个高级数据结构,在修改和查询中复杂度已经超过了分块成功的到达了 l o g ( n ) log(n) 的级别,算是非常快了。


二、线段树的思想

线段树的思想类似于分治,就是不停地将一个区间分成一个又一个小区间,类似于分块,但显然更优。

例如对于一个 [ 1 , 10 ] [1,10] 的区间,他的图如下:

在这里插入图片描述

虽然有点模糊,不过大体是能够看清楚的。

他分区间的过程类似如下:
[ 1 , 10 ] ( m i d = 5 )   >   [ 1 , 5 ]   & &   [ 6 , 10 ] [1,10](mid=5)\ ->\ [1,5]\ \&\&\ [6,10]
[ 6 , 10 ] ( m i d = 8 )   >   [ 6 , 8 ]   & &   [ 9 , 10 ] [6,10](mid=8)\ ->\ [6,8]\ \&\&\ [9,10]
[ 1 , 5 ] ( m i d = 3 )   >   [ 1 , 3 ]   & &   [ 4 , 5 ] [1,5](mid=3)\ ->\ [1,3]\ \&\&\ [4,5]
[ 6 , 8 ] ( m i d = 7 )   >   [ 6 , 7 ]   & &   [ 8 , 8 ] [6,8](mid=7)\ ->\ [6,7]\ \&\&\ [8,8]
………………………………………………

就是一直递归,二分的过程。

同时,它每一个点代表的都是一个区间,也能代表一个区间的和以及极值,这也为我们求区间极值或者区间和提供了一个方法。(balabla)


三、线段树的基本操作

一、建树(可能不需要)
如果是建树,题目中一般会给点权或者边权,有了这些基础的数值,就有了建树的内容。
我们以这题为例。
题目给了每个点的点权,同时也要求我们求区间内的数值和,也就是边权和,所以我们在建图时就可以把初始的区间的权值和给求好,这样我们求修改后的数值就会方便多了。

建图不难建,只需要我们用搜索建图即可。

Code

LL build(int x,int l,int r){
	if (l==r) return tr[x].sum=num[l];//单独的权值
	int mid=l+r>>1;
	tr[x].sum+=build(x<<1,l,mid)+build(x<<1|1,mid+1,r);//将区间和累加上
	return tr[x].sum;
}

当然也有求最大值的,只需要将代码稍微改变一下即可

Code

LL build(int x,int l,int r){
	if (l==r) return tr[x].Max=num[l];//单独的权值
	int mid=l+r>>1;
	tr[x].Max=max(build(x<<1,l,mid),build(x<<1|1,mid+1,r));//将区间和累加上
	return tr[x].sum;
}

二、区间修改
区间修改有点麻烦,一般都是在 [ x , y ] [x,y] 区间内每个 元素加上 k k
还是这张图为例:
在这里插入图片描述

  • 假设我们要改变 [ 1 , 3 ] [1,3] 的值,我们就可以直接在 [ 1 , 3 ] [1,3] 出进行修改
  • 假设我们要改变 [ 1 , 7 ] [1,7] 的值,那么我们就需要改变 [ 1 , 5 ] [1,5] 的值和 [ 6 , 7 ] [6,7] 的值。

这是我们就需要用一个 l a z y   t a g ( ) lazy\ tag(懒标记)
为什么说是懒标记呢?那是因为一般的改变都是直接对原值进行 改变,但是lazy tag确实对一个区间加上一个需要改变的值。
什么意思呢?
我们假设要在 [ 1 , 3 ] [1,3] 加上一个值3
一般的程序是直接将三个数直接加上3,但是lazy tag确实在 [ 1 , 3 ] [1,3] 节点上加上一个3的标记,表示当前点所包含的区间需要加上数值3,有了这个标记之后,我们就可以在递归的时候顺便将值待下去,不需要多耗费时间,时间自然也节省了。

那么还是以这题的修改为例

Code

void change(int x,int l,int r){
	if (l>R || r<L) return;
	if (l>=L&&r<=R){
		tr[x].tag+=k;//tag加上k
		tr[x].sum+=(r-l+1)*k;//区间加上那么多
		return;
	}
	int ls=x<<1,rs=x<<1|1,mid=l+r>>1;
	if (tr[x].tag){
		tr[ls].tag+=tr[x].tag;
		tr[rs].tag+=tr[x].tag;
		tr[ls].sum+=(mid-l+1)*tr[x].tag;
		tr[rs].sum+=(r-mid)*tr[x].tag;
		tr[x].tag=0;//别忘了赋0
	}//对子区间进行改变
	change(ls,l,mid);
	change(rs,mid+1,r);
	tr[x].sum=tr[ls].sum+tr[rs].sum;/返回的时候重新累加上
	return;
}

三、区间查询
区间查询挺好做,仍然是递归。
只不过在求和时还需要在进行一下 p u s h d o w n   ( ) pushdown\ (对子区间进行改变) 操作,因为在 c h a n g e change 时很可能因为查询到了该区间而并没有继续往下改变,在查询时就会出问题,所以我们就要进行一次操作。其他的就按照dfs返回值即可。

那么仍然是这题

Code

LL findsum(int x,int l,int r){
	if (l>R || r<L) return 0;
	if (l>=L && r<=R) return  tr[x].sum;//如果包含区间,直接返回
	int ls=x<<1,rs=x<<1|1,mid=l+r>>1;
	if (tr[x].tag){
		tr[ls].tag+=tr[x].tag;
		tr[rs].tag+=tr[x].tag;
		tr[ls].sum+=(mid-l+1)*tr[x].tag;
		tr[rs].sum+=(r-mid)*tr[x].tag;
		tr[x].tag=0;
	}//修改
	return findsum(ls,l,mid)+findsum(rs,mid+1,r);//直接返回
}

例题精讲

因为我目前仍然没有做多少线段树的题目,所以还是只能这题了。。

那么将之前的三个代码合并一下就变成了完整的代码

Code

#include<bits/stdc++.h>
using namespace std;
#define LL long long
struct node{
	LL sum,tag;
}tr[400010];
LL num[400010];
LL k;
int n,m,L,R;
LL build(int x,int l,int r){
	if (l==r) return tr[x].sum=num[l];
	int mid=l+r>>1;
	tr[x].sum+=build(x<<1,l,mid)+build(x<<1|1,mid+1,r);
	return tr[x].sum;
}
void change(int x,int l,int r){
	if (l>R || r<L) return;
	if (l>=L&&r<=R){
		tr[x].tag+=k;
		tr[x].sum+=(r-l+1)*k;
		return;
	}
	int ls=x<<1,rs=x<<1|1,mid=l+r>>1;
	if (tr[x].tag){
		tr[ls].tag+=tr[x].tag;
		tr[rs].tag+=tr[x].tag;
		tr[ls].sum+=(mid-l+1)*tr[x].tag;
		tr[rs].sum+=(r-mid)*tr[x].tag;
		tr[x].tag=0;
	}
	change(ls,l,mid);
	change(rs,mid+1,r);
	tr[x].sum=tr[ls].sum+tr[rs].sum;
	return;
}
LL findsum(int x,int l,int r){
	if (l>R || r<L) return 0;
	if (l>=L && r<=R) return  tr[x].sum;
	int ls=x<<1,rs=x<<1|1,mid=l+r>>1;
	if (tr[x].tag){
		tr[ls].tag+=tr[x].tag;
		tr[rs].tag+=tr[x].tag;
		tr[ls].sum+=(mid-l+1)*tr[x].tag;
		tr[rs].sum+=(r-mid)*tr[x].tag;
		tr[x].tag=0;
	}
	return findsum(ls,l,mid)+findsum(rs,mid+1,r);
}
int main(){
	scanf("%d %d",&n,&m);
	for (int i=1;i<=n;i++) scanf("%lld",&num[i]);
	build(1,1,n);
	for (int i=1,o;i<=m;i++){
		scanf("%d %d %d",&o,&L,&R);
		if (o==1) scanf("%lld",&k),change(1,1,n);
		else printf("%lld\n",findsum(1,1,n));
	}
//	for (int i=1;i<=9;i++)
//	  cout<<tr[i].sum<<endl;
	return 0;
}

请大家支持我

猜你喜欢

转载自blog.csdn.net/huang_ke_hai/article/details/88876539