数据结构-一维树状数组维护区间最大值
写在前面,网上的一些教程实在一言难尽,,,当然我这篇也是(狗头)。所以我打算写一篇自己可以理解的树状数组维护区间最值,以最大值举例。需要树状数组维护区间和的相关知识,好,进入正题。
1 前言
c[i]的意义:c[i]代表原数组区间[i-lowbit(i)+1,i]内的最大值。
和维护区间和类似,c[i]仍对应长度为lowbit(i)的分块。
2 单点更新
如果我们求维护区间和,我们是这么写的:
void update(int i,int k)//k代表原数组第i个元素增加量
{
while(i<=n)
{
c[i]+=k;
i+=lowbit(i);
}
}
那我们维护区间最大值,能不能这么写呢?试着写一下:
void update(int i,int k)//这里k代表把原数组第i个元素变成k,意义不同问题不大。
{
while(i<=n)
{
c[i]=max(c[i],k);
i+=lowbit(i);
}
}
好像很正确的样子,因为c[i]由k组成,c[i]又直接关系到c[i+lowbit(i)],这么一路改过去就好了。
但是有一个例外,设数组为arr,如果恰巧c[i]=arr[i],即c[i]管辖的区间的最大值就是arr[i],且恰巧区间内其他值也没有等于arr[i]的,而咱们还恰好更改了arr[i],而且这个值k恰巧小于原来的arr[i],那么想想看,arr[i]变了之后c[i]就不可能等于原arr[i],但是add函数仍让我们c[i]等于原arr[i]。那肯定错误了。再极端一下,原arr[i]就是原数组的最大值,那岂不错的更离谱了!??
这只是一个情况,更多细节各位可以自己捋清楚。
那要怎么办?
一种方法就是清空数组c,再重新构造一次,那么时间复杂度会是(nlogn)
还有一种方法,就是比较直接相关的几个值,按图找规律就好,比如,如图:c[8]=max{arr[8],c[4],c[6],c[7]}
,二进制就是c[1000]=max{arr[8],c[100],c[110],c[111]}
。观察出规律就能写了...(蒟蒻更深层次的还理解不了),也就是:
(个人不会画图,用的是区间和那个,看图就行,不用在意其它文字,图片出自 https://blog.csdn.net/bestsort/article/details/80796531)
下面是奇丑无比的代码:
void update(int i,int k)
{
int lb;
c[i]=arr[i]=k;//原数组也要跟着改变,求区间最大值要用
while(i<=n)
{
lb=lowbit(i);
c[i]=arr[i];
for(int j=1;j<lb;j<<=1)
c[i]=max(c[i],c[i-j]);
i+=lowbit(i);
}
}
这样的时间复杂度为\(O((logn)^2)\)
3 区间查询
如果查询区间[lef,rig]的最大值,和维护区间和不一样,需要用上原数组。由于每个c[i]对应的区间是往左的,我们对rig进行讨论。
设查询函数为query。
理解一下思想就好,没必要用递归
奇丑无比的代码:
int query(int lef,int rig)
{
int res=0;
while(rig>=lef)
{
if(rig-lowbit(rig)+1>=lef)
{
res=max(res,c[rig]);
rig=rig-lowbit(rig);
}
else
{
res=max(res,arr[rig]);
--rig;
}
}
return res;
}
时间复杂度为\(O((logn)^2)\)
补充
其实这么写单点更新也是可以的:
void update(int i,int k)//这里k代表把原数组第i个元素变成k,意义不同问题不大。
{
while(i<=n)
{
c[i]=max(c[i],k);
i+=lowbit(i);
}
}
不过对应特殊情况,比如求最长上升子序列等问题,因为对于dp问题,我们每次要求选择最优,比如,连续上升子序列问题中,若k小于等于arr[i],那么我们就不用替换,就可以避免之前说的问题。这样我们可以用时间复杂度仅为O(logn)的函数完成单点更新。
个人片面的理解,如有疏漏,欢迎指出。