线段树 区间最值查询 / 区间更新 简单实现

线段树定义

线段 = 一维数组
如图:按照下标,二分的将一个一维数组,分为两半,每个线段树节点都代表着原数组中的一段子区间,而叶子节点则代表着长度为1的区间
在这里插入图片描述

线段树可以用来干什么

主要是解决区间问题,比如查询一个区间的最值,给一个区间所有元素加减一个数,或者是求取某个区间的和,对于静态的区间,我们都有比较好的方法,比如【前缀和与差分数组】,【ST做RMQ静态查询

可是这些方法都有缺点:如果我们的数组是动态更新的呢?即边更新边查询。

  • 线段树可以实现动态的更新区间,同时较快的查询 指O(log(n))

线段树结构

基于上面一大堆特性,一个线段树的节点,需要有:

  • 当前代表的是那个区间
  • 左右孩子
  • 存储的值(比如当前代表的区间的最大值)

如果用数组(即完全二叉树)来存储的话,可能还要加上

  • 在数组中的下标

可以很快写出结构的定义

typedef struct node
{
	int l, r, i, val;
}node;

其实都不用这么麻烦的,只用存 【val】,即该线段树节点存储的值就行了,可以用一维数组来存了

我们用数组模拟完全二叉树存放节点,但是因为二分的特性,我们不管是查找还是更新,都可以直接递推出当前节点代表的子区间,所以存储只需要存储节点的值就可以了,注意数组开大点,因为如果满打满算的话,实际节点数是 1 + 2 + 4 + 8 + ... + n/2 + n,即满二叉树

int tree[maxn*4];

线段树创建

用数组保存节点,熟悉二叉树的话就会很快手
在这里插入图片描述
当前节点在数组中的下标是 i,那么左孩子下标 2*i, 右孩子下标 2*i+1

同样递归创建,已知原数组用 a[] 保存,trees[] 数组保存节点的值,即该节点代表的区间的最大元素的值

设当前要创建的区间是 [l, r],tree 数组中 i 下标代表这个区间

  • 如果 l=r,那么 tree[i] = a[l],终止递归
  • 否则,递归左右子树,即创建两个对半分的子区间,他们在tree中的下标:2*i 与 2*i+1,递归结束后更新 i 下标,即 tree[i] = max(tree[2*i], tree[2*i+1])
/*
i : 当前线段树节点在tree数组中的下标
l : 当前线段树节点代表的区间的左端点
r : 当前线段树节点代表的区间的右端点
*/
void creat(int i, int l, int r)
{
	if(l==r) {tree[i]=a[l]; return;}
	creat(2*i, l, (l+r)/2), creat(2*i+1, (l+r)/2+1, r);
	tree[i] = max(tree[2*i], tree[2*i+1]);
}

线段树查询

查询一个区间,因为我们二分地掌握着一些区间的值(即存在trees数组中),那么只要找到一定的区间,使得这些区间拼接起来等于【要查找的区间】,然后合并这些区间的答案即可,仍然使用递归查询,而且我们总能找到几个合适的区间,因为掌握的区间最小都是长度为1的,不行就硬凑一个出来呗

设查询的区间是 Q 我们只要找到所有 Q 的子区间即可,然后合并答案,这个过程使用递归

  • 如果当前递归的节点所代表的区间,和 Q 没有交集,那么停止递归,返回最小值
  • 如果当前递归的节点所代表的区间,是 Q 的子区间,那么停止递归,返回当前区间存储的值
  • 否则将当前递归的节点所代表的区间对半分,递归左右子区间
/*
ql : 要查询的区间左端点
qr :要查询的区间右端点
i  : 当前线段树节点在tree数组中的下标
l  : 当前线段树节点代表的区间左端点
r  : 当前线段树节点代表的区间右端点
*/
int query(int ql, int qr, int i, int l, int r)
{
	if(qr<l || r<ql) return INT_MIN;
	if(ql<=l && r<=qr) return tree[i];
	return max(query(ql, qr, 2*i, l, (l+r)/2), 
			   query(ql, qr, 2*i+1, (l+r)/2+1, r));
}

线段树区间更新

更新一个区间内的所有数字,朴素复杂度nlog(n),有点慢了,我们引入一种缓存的机制,就是把当前区间要全体增加的数字缓存起来,等我们查询的时候,可以根据这个数字来做加减

int buffer[maxn*4];

可是难题就在于如果执行多次更新操作,而且这些更新的区间各不相同,甚至还有交集,那么情况变得麻烦起来

对于更新一个区间Q,如果已知缓存了这个区间要更新的数字 v,那么这个区间最大值的结果,会加上v,我们直接修改存储在tree数组里面的值,即tree[i]+=v,于是我们可以马上地更新到这个区间的最大值,然后把问题丢给下一层的两个子区间,即左右子区间的缓存增加了!

这样做的好处是:我们需要少量的操作次数就可以完成一个区间的修改,而剩下的操作留给查询的时候 “顺路做了”

值得一提的是

  • 更新时,只有在当前区间是Q的子区间时,才能缓存要增加的值,然后把问题丢给下一层的子区间,同时更新这一层的答案,然后返回
  • 如果更新时需要递归到下一层,别忘了先抛出这一层的缓存,再操作
  • 递归调用更新之后,return之前记得更新这一层的值
  • 查询之前,总是要记得清空当前区间的缓存,即把问题丢给下一层
/*
清空在tree数组中i下标代表的区间的缓存,向下一层抛出缓存
*/
void clear_buf(int i)
{
	// 更新当前节点存储的值 
	tree[i] += buffer[i];
	// 将缓存传递到下一层的节点 
	buffer[2*i] += buffer[i];
	buffer[2*i+1] += buffer[i];
	// 清空当前节点缓存 
	buffer[i] = 0;
}

/*
ul  : 要更新的区间左端点
ur  : 要更新的区间右端点
i   : 当前区间的值存储在tree数组i下标
l   : 当前区间左端点
r   : 当前区间右端点
val : 要更新的值
*/
void update(int ul, int ur, int i, int l, int r, int val)
{
	if(ur<l || r<ul) return;
	if(ul<=l && r<=ur)
	{
		buffer[i] += val;
		clear_buf(i);
		return;
	}
	clear_buf(i);
	update(ul, ur, 2*i, l, (l+r)/2, val);
	update(ul, ur, 2*i+1, (l+r)/2+1, r, val);
	tree[i] = max(tree[2*i], tree[2*i+1]);
}

同时查询函数也要记得向下抛出缓存

int query(int ql, int qr, int i, int l, int r)
{
	if(qr<l || r<ql) return INT_MIN;
	clear_buf(i);
	if(ql<=l && r<=qr) return tree[i];
	return max(query(ql, qr, 2*i, l, (l+r)/2), 
			   query(ql, qr, 2*i+1, (l+r)/2+1, r));
}

代码

#include <bits/stdc++.h>

using namespace std;

#define maxn 1000

int tree[maxn*4];
int buffer[maxn*4];
int a[maxn], n;

void creat(int i, int l, int r)
{
	if(l==r) {tree[i]=a[l]; return;}
	creat(2*i, l, (l+r)/2), creat(2*i+1, (l+r)/2+1, r);
	tree[i] = max(tree[2*i], tree[2*i+1]);
}

void clear_buf(int i)
{
	// 更新当前节点存储的值 
	tree[i] += buffer[i];
	// 将缓存传递到下一层的节点 
	buffer[2*i] += buffer[i];
	buffer[2*i+1] += buffer[i];
	// 清空当前节点缓存 
	buffer[i] = 0;
}

int query(int ql, int qr, int i, int l, int r)
{
	if(qr<l || r<ql) return INT_MIN;
	clear_buf(i);
	if(ql<=l && r<=qr) return tree[i];
	return max(query(ql, qr, 2*i, l, (l+r)/2), 
			   query(ql, qr, 2*i+1, (l+r)/2+1, r));
}

void update(int ul, int ur, int i, int l, int r, int val)
{
	if(ur<l || r<ul) return;
	if(ul<=l && r<=ur)
	{
		buffer[i] += val;
		clear_buf(i);
		return;
	}
	clear_buf(i);
	update(ul, ur, 2*i, l, (l+r)/2, val);
	update(ul, ur, 2*i+1, (l+r)/2+1, r, val);
	tree[i] = max(tree[2*i], tree[2*i+1]);
}

int main()
{	
	memset(buffer, 0, sizeof(buffer)); 
	cin>>n;
	for(int i=1; i<=n; i++) cin>>a[i];
	creat(1, 1, n);
	
	update(1, 4, 1, 1, n, 10);
	update(3, 4, 1, 1, n, 10);
	
	for(int l=1; l<=n; l++)
		for(int r=l; r<=n; r++)
			cout<<l<<" "<<r<<" "<<query(l, r, 1, 1, n)<<endl;
	
	return 0;
}

/*
8
1 2 3 4 5 6 7 8
*/
8
1 2 3 4 5 6 7 8
[1, 1] 11
[1, 2] 12
[1, 3] 23
[1, 4] 24
[1, 5] 24
[1, 6] 24
[1, 7] 24
[1, 8] 24
[2, 2] 12
[2, 3] 23
[2, 4] 24
[2, 5] 24
[2, 6] 24
[2, 7] 24
[2, 8] 24
[3, 3] 23
[3, 4] 24
[3, 5] 24
[3, 6] 24
[3, 7] 24
[3, 8] 24
[4, 4] 24
[4, 5] 24
[4, 6] 24
[4, 7] 24
[4, 8] 24
[5, 5] 5
[5, 6] 6
[5, 7] 7
[5, 8] 8
[6, 6] 6
[6, 7] 7
[6, 8] 8
[7, 7] 7
[7, 8] 8
[8, 8] 8
发布了262 篇原创文章 · 获赞 11 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_44176696/article/details/105187658
今日推荐