数据结构-一维树状数组维护区间最大值

写在前面,网上的一些教程实在一言难尽,,,当然我这篇也是(狗头)。所以我打算写一篇自己可以理解的树状数组维护区间最值,以最大值举例。需要树状数组维护区间和的相关知识,好,进入正题。

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]}。观察出规律就能写了...(蒟蒻更深层次的还理解不了),也就是:

\[c[i]=max\{arr[i],c[i-lowbit(i)/2],c[i-lowbit(i)/2^2],,,c[i-2^0]\} \]

(个人不会画图,用的是区间和那个,看图就行,不用在意其它文字,图片出自 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。

\[如果rig-lowbit(rig)+1>=lef,res=max(query(lef,rig-lowbit(rig)),c[rig]) \]

\[如果rig-lowbit(rig)+1>=lef,res=max(query(lef,rig-1),arr[rig]) \]

理解一下思想就好,没必要用递归
奇丑无比的代码:

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)的函数完成单点更新。

个人片面的理解,如有疏漏,欢迎指出。

posted @ 2021-02-11 00:50  七铭的魔法师  阅读(627)  评论(1编辑  收藏  举报