树状数组入门及例题题解(三)——区间最值

用树状数组求区间最值

当会用树状数组求区间和了之后 将数组的意义稍微改变 将代码稍作修改 便可以得到用树状数组求区间最值的代码

本篇博客对于求区间最值 以求区间最大值为例 读者可以读完之后 尝试写一写求区间最小值的代码

在以下的讨论中
a[i]代表原区间的元素
c[i]代表树状数组求区间前缀和的元素
h[i]代表求区间最大值的元素

一些关键的说明 使用树状数组进行区间求和 以及lowbit()函数执行的原理详见博客
树状数组入门及例题讲解(一)——区间求和及单点修改

在进行区间求和的代码中
c数组元素 c[i] 代表的是区间中的 第 i-lowbit(i)+1 个元素 到 第 i 个元素的和

要求区间最值
我们相类似的创建一个数组 h[maxn] 其中
数组元素 h[i] 代表的是 区间中的 第 i-lowbit(i)+1 个元素 到 第 i 个元素的最大值

单点修改操作

那么对于单点修改 当然仍然与 区间求和的单点修改相差不大 只是不再求和 而是进行取最大值的操作

一个错误代码

void update(int position,int value){
	while(position<=right_restrain){
		h[position]=max(h[position],value);
		position+=lowbit(position);
	}
}

这个代码为什么错呢

因为如果假设改动的这个点正好是在这个区间里面取得最大值的点
而改变之后 使得其不再是最大值了 那程序就出错了

所以这里需要对这个代码进行修改
一个比较简单的想法就是 因为这个点的变动而导致最大值改变的区间 也就是h数组中的 包含有a[i] 这个元素的所有 h[i] 元素 我们先将其归零 然后再从头到尾再来一遍
即如下代码:

void update(int position,int value){
	//表示要将区间position的位置的元素的值改变为value
	a[position]=value;
	memset(h,0,sizeof(h));
	//之所以要归零 是因为可能有的区间取到的最大值正好是 a[position]这个元素 
	//为了抵消这个影响 就只能归零(假设我们讨论的都是正数) 然后从头再来了
	for(int i=1;i<=right_restrain;i++){
		int k=i;
		while(k<=right_restrain){
			h[k]=max(h[k],a[k]);
			k+=lowbit(k);
		}
	}

那现在 显然复杂度达到了O(nlogn) 复杂度太高 已经体现不出树状数组的优势了 所以要对这个算法进行优化

单点修改操作的原理

先来看看单点修改操作的目的:
改变某一个位置的元素 的值 然后更新 h数组中的区间最大值
而在前面也已经说了
对于 h[i] 这个元素而言 它所表示的是 i-lowbit(i)+1 到 i 的这个区间的元素的最大值
那么自然 我们改变 a[i] 这个元素 对于 h[1] h[2] ……h[i-1] 这些元素都是没有影响的

那么此时可以有一个大胆的想法 要改变 h[i] 这个东西
因为前面的元素 在我们进行单点修改操作时 是不受影响的 所以我们可以先求出
i-lowbit(i)+1 到 i-1 这个区间内的最大值 然后再与 a[i] 这个改变了的元素比较
从而得出 i -lowbit(i)+1 到 i 这个区间的最大值 也就是 h[i] 这个元素的值就被我们求出来了
于是 现在的问题就转变为 缩小范围 找到 i-lowbit(i)+1 到 i-1 这个区间内的最大值
对于这个过程 举个例子来说:
假设 i = …… 010000
那么首先就肯定要从 i-1 开始查出这个最大值
i-1 = ……001111
于是 h[i-1] 所表示的区间最大值的范围就是 ……001111 ~ ……001111 (这里正好就只包括了他自己)
对于 这个大区间 i-lowbit(i)+1 到 i-1 已经找到了 i-1 的最大值
那么接下来 范围缩小 只需要寻找 i-lowbit(i)+1 到 i-2 这个 区间内的最大值了
可知 i-2 =……001110
于是 h[i-2] 所表示的区间最大值就是 ……001101 ~ ……001110 这个区间内的最大值
接下来 要求的就是 ……~ ……001100 的最大值
……
我们将这些需要访问的区间的右端点 (也就是访问的h数组中的元素的索引)拿出来看看
可以发现 我们要比较的就是
a[……010000]
h[……001111]
h[……001110]
h[……001100]
h[……001000]
于是可以总结规律如下(以下均以二进制的形式表示):
i=……010000

a[……010000]
h[……001111] ……001111=……010000-1 # —— i-1
h[……001110] ……001110=……010000-10 #—— i- (1<<1)
h[……001100] ……001100=……010000-100 #—— i-(1<<2)
h[……001000] ……001000=……010000-1000 #—— i-(1<<3)

直至索引为 (1< 那么将这个过程转化为程序就可以写成:

void update(int position,int value){
	//表示需要将区间position的位置的元素改变为value
	int len;
	a[position]=value;
	while(position<=right_restrain){
		len=lowbit(position);
		h[position]=a[position];
		
		for(int i=1;i<len;i<<=1){
			h[position]=max(h[position],h[position-i]);
		}
		
		position+=len;
	}
}

那么此时的复杂度 显而易见的 达到了 O( (logn)^2 )

区间最值

区间最值的求法 也与区间求和的方法不太一样
因为 如果要求的是 区间 i到j 的最大值 并不能像区间求和那样简单的用一个减法就可以实现 现在似乎乍一看没有什么思路 那我就来仔细说明一下:

对于一个区间 i 到 j 而言 要求这个区间的最大值 就要将 这个区间内的所有元素都进行一次比较
首先定义一个ans=0

那么就有两种方法:
1、这样来看 如果 目标区间 完全包括了h[i] 所代表的 i-lowbit(i)+1 到 i 的这个区间那么就很简单 直接把 h[i] 与 ans比较一下就可以了 这样就缩小了区间范围

2、但是有时候可能运气没有这么好 并没有只包含了一部分 那虽然包含了一部分 但是依然会有区间外的元素对这个最大值产生的影响 所以不可用 就把 ans 与 a[i] 比较 然后依然可以缩小区间范围

于是把这个过程写成程序就可以了,代码如下:

int find_max(int left,int right){
	//找到区间 left 到 right  内的最大值
	int ans=0;
	while(right>=left){
		ans=max(ans,a[right]);
		right--;
		
		while(right-lowbit(right)+1>=left){
			ans=max(ans,h[right]);
			right-=lowbit(right);
		}
	}
	
	return ans;
}

以上就是使用树状数组求最值的全部内容了 读者可以再次尝试一下 用树状数组求区间最小值。

入门例题

树状数组入门及例题讲解(四)——I Hate It

你可能感兴趣的:(#,树状数组,acm竞赛,数据结构)