树状数组 求区间最值、求区间和 详解

从代码开始

以下就是树状数组的核心代码。

int lowbit(int x)
{
	return x&-x;
}
void add(int i,int x)
{
	while(i<=n)
	{
		c[i]+=x;
		i+=lowbit(i);
	}
}
int get(int i)
{
	int ans=0;
	while(i)
	{
		ans+=c[i];
		i-=lowbit(i);
	}
	return ans;
}

简介

树状数组能维护区间最值、区间和,查询、增加都是 O ( log ⁡ n ) O(\log n) O(logn),十分常用。
基本思想:树状的分块前缀和(不是

分析代码

add部分(改变某数的值)

a d d add add 函数大概表示这样一个功能:
首先将一个数的最后一位 1 1 1 进一位(即 i + = l o w b i t ( i ) \color{red}i+=lowbit(i) i+=lowbit(i)部分,其中 l o w b i t ( i ) \color{red}lowbit(i) lowbit(i) 详见这里)
然后将 c [ i ] \color{red}c[i] c[i] 加上要加的值(可参见下面的图,表示了每个 i + = l o w b i t ( i ) \color{red}i+=lowbit(i) i+=lowbit(i)后会到哪里)

树状数组 求区间最值、求区间和 详解_第1张图片
比如,将 5 5 5加上 1 1 1就要将 6 6 6 8 8 8都加上1
树状数组 求区间最值、求区间和 详解_第2张图片
所以,有什么用呢?

get部分(求 1 − i 1-i 1i所有数的和)

同理, g e t get get 函数表示这样一个功能:
首先将一个数的最后一位 1 1 1 变为 0 0 0
然后收集 c [ i ] \color{green}c[i] c[i] 的值(可参见下面的图,绿线表示了每个 i − = l o w b i t ( i ) \color{green}i-=lowbit(i) i=lowbit(i)后会回到哪里)
树状数组 求区间最值、求区间和 详解_第3张图片
可以发现,如果求 n n n,就永远不会沿绿线到 > n >n >n的数,也不会回到 n n n的儿子( n n n的儿子的值已经加到 n n n中),所以,即可沿路求得 1 − n 1-n 1n所有数的和

如果换一种方式,可能会清晰一点。
树状数组 求区间最值、求区间和 详解_第4张图片
其中,在外面的表示它已经加上了(包括了)里面的值。
例如:求 1 − 7 1-7 17的和 = 7 + ( 1 − 6 的和 ) = 7 + 6 [ 包括 5 ] + ( 1 − 4 的和 ) = 7 + 6 [ 包括 5 ] + 4 [ 包括 1 − 3 ] =7+(1-6的和)=7+6{\color{green}[包括5]}+(1-4的和)=7+6{\color{green}[包括5]}+4{\color{green}[包括1-3]} =7+(16的和)=7+6[包括5]+(14的和)=7+6[包括5]+4[包括13]

那么,代码如何实现?

再放一遍代码:

int lowbit(int x){return x&-x;}
void add(int i,int x)
{
	while(i<=n){c[i]+=x;i+=lowbit(i);}
}
int get(int i)
{
	int ans=0;
	while(i){ans+=c[i];i-=lowbit(i);}
	return ans;
}

**

初始数组:相当于 0 0 0加上初始值,只要用 a d d add add函数即可
将某个数增加几:用 a d d add add函数即可
返回一段和 : g e t get get函数可以求 1 − n 1-n 1n的和,因此只要 g e t ( r ) − g e t ( l − 1 ) = S U M ( 1 , r ) − S U M ( 1 , l − 1 ) = S U M ( l , r ) get(r)-get(l-1)=SUM(1,r)-SUM(1,l-1)=SUM(l,r) get(r)get(l1)=SUM(1,r)SUM(1,l1)=SUM(l,r)
返回一个数 :只要 g e t ( x ) − g e t ( x − 1 ) = a [ x ] get(x)-get(x-1)=a[x] get(x)get(x1)=a[x]
将一段增加几:类似差分,增加一段后还要减回来。 a d d ( r , x ) ; a d d ( l , − x ) add(r,x);add(l,-x) add(r,x);add(l,x)
树状数组 求区间最值、求区间和 详解_第5张图片

比如,将 [ 2 , 5 ] [2,5] [2,5]增加 5 5 5,结果如上图,其中未被框起来的± 5 5 5是因为包括了要处理的数而被动加减。

如何维护区间最值?

思路:因为是大区间获取最值时会与小区间取 m a x max max,所以可以每次更新时只取儿子的 m a x max max,而不用照顾到父亲的值(与求和不同)

更改add函数 (但是叫updata更合适)

要与儿子们取max进行更新,但不能取全部的儿子,比如更新8时要更新7个,会达到 O ( n ) O(n) O(n)

树状数组 求区间最值、求区间和 详解_第6张图片
比如对于更新 4 4 4 2 & 3 2\&3 2&3是真儿子,而 1 1 1已经被 2 2 2更新过,不用再更新(因为1一定 < = 2 <=2 <=2 )。
经过观察(别问我是怎么观察的),可得 n n n的真儿子为 n − 2 x [ x < = log ⁡ l o w b i t ( n ) ] n-2^x[x<=\log lowbit(n)] n2x[x<=loglowbit(n)],说人话,就是

  for(int t=1;t

所以就简单了。

void add(int i,int x)
{
    c[i]=x;
    for(int t=1;t

更改get函数

只要找 [ l , r ] [l,r] [l,r]中真儿子更新即可。

int get(int l,int r)
{
    int ans=a[r];
    while(l<=r)
    {
        ret=max(ret,a[r]);
        for(--r;r-l>=lowbit(r);r-=lowbit(r))ans=max(ans,c[r]);
    }
    return ret;
}

你可能感兴趣的:(c++)