树状数组的应用之区间最值维护和查询(个人的学习理解)

对于区间最值的查询和维护,用线段树也能完成.可是树状数组比较好写,时间复杂度也不高,不用白不用-。-
用树状数组求区间最值时,先得想一个问题:lowbit(x)在树状数组的作用究竟是什么.
在树状数组最基本区间求和中tree[x]代表的是tree[x-lowbit(x)+1,x]的和.比如tree[8]=arr[1]+arr[2]+…+arr[8]
由此不难发现lowbit可以表示区间的长度.这点在求区间最值的时候比较重要.
既然用来求区间最值问题,那么原来的树状数组模板也得变变,update函数由原来的区间和更新变成区间最值更新,所以此时tree[x]表示的是tree[x-lowbit(x)+1,x]的最大值.

void update(int x,int val)
{
	while(x<=n)
	{
		tree[x]=MAX(tree[x],val);
		x+=lowbit(x);
	}
}

现在tree中保存的都是对应区间的最大值了.那怎么求[L,R]范围的最大值呢.如果从L一路莽到R的话,是可以求出最大值,不过求出的最大值可能是[1,R]之间的最大值,并不是所需要的[L,R]之间的最大值.别忘了,tree在更新时是由下至上,且根据lowbit划分了每个区间(例如:tree[3]=arr[3],tree[4]=arr[1]~arr[4]).所以问题又来了,怎么避免[1,L-1]呢.既然如此不如从右往左开始查询,len表示[L,R]的长度,len=R-L+1.前面提到过tree[x]=[x-lowbit(x)+1,x]之间的最大值,lowbit(x)可以表示区间长度,所以如果len>= lowbit(R),那么tree[R]可以直接取出来比较ans=max(ans,tree[R]).例如求[4,7]之间的最大值,len=4,lowbit(7)=1(tree[7]=arr[7]).那如果lowbit(R)>len呢,这个好办,直接拿arr[R]来比较就行了,ans=max(ans,arr[R]).
HDU1754经典的区间最值问题

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#define lowbit(x) ((x)&(-x))
#define MAX(a,b) (((a)>(b))?(a):(b))
using namespace std;
int tree[200010],arr[200010],n;
void update(int x,int val)
{
	while(x<=n)
	{
		tree[x]=MAX(tree[x],val);
		x+=lowbit(x);
	}
}
int query(int L,int R)
{
	int len=R-L+1,ret=0;
	while(len&&R)
	{
		if(len>=lowbit(R))
		{
			ret=MAX(tree[R],ret);
			len-=lowbit(R);
			R-=lowbit(R);
		} 	
		else
		{
			ret=MAX(arr[R],ret);
			len--,R--;
		}
	}
	return ret;
} 
int main()
{
	int m;
	while(scanf("%d%d",&n,&m)!=EOF)
	{
		memset(tree,0,sizeof(tree));
		for(int i=1;i<=n;i++)
		{
			scanf("%d",&arr[i]);
			update(i,arr[i]);
		}
		while(m--)
		{
			char s[2];
			int a,b;
			scanf("%s%d%d",s,&a,&b);
			if(s[0]=='U')
			{
				arr[a]=b;
				update(a,b);
			}
			else
			{
				int ans=query(a,b);
				printf("%d\n",ans);
			}
		}
	}
	return 0;
} 

如有错误烦请指出,感激不尽

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
树状数组可以用来维护一个序列的前缀和,从而实现快速区间查询。但是如果想要求区间最大值或最小值,需要进行一些修改。 具体做法如下: 1. 定义一个数组 $maxn$,表示每个节点的最大值。 2. 在更新某个节点的值时,同时更新其父节点的最大值。 3. 在查询区间最大值时,从右往左遍历每个节点,找到第一个其右边的兄弟节点的最大值大于等于查询区间左端点的位置,然后从该兄弟节点开始往右遍历,找到第一个其左边的兄弟节点的最大值小于等于查询区间右端点的位置。这两个位置之间的节点的最大值即为查询区间的最大值。 示例代码: ```cpp const int N = 1e5; int a[N], c[N], maxn[N]; void update(int x, int v) { a[x] = v; while (x <= N) { c[x] += v; maxn[x] = max(maxn[x], v); x += x & -x; } } int query(int l, int r) { int res = INT_MIN; for (int i = r; i >= l; ) { if (i - (i & -i) + 1 >= l) { res = max(res, maxn[i]); i -= i & -i; } else { res = max(res, a[i]); i--; } } return res; } ``` 其中,$a$ 数组表示原始序列,$c$ 数组表示树状数组,$maxn$ 数组表示每个节点的最大值。在更新节点 $x$ 的值时,同时更新 $maxn$ 数组。在查询区间最大值时,从右往左遍历每个节点,找到第一个其右边的兄弟节点的最大值大于等于查询区间左端点的位置,然后从该兄弟节点开始往右遍历,找到第一个其左边的兄弟节点的最大值小于等于查询区间右端点的位置。这两个位置之间的节点的最大值即为查询区间的最大值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值