以下就是树状数组的核心代码。
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),十分常用。
基本思想:树状的分块前缀和(不是
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)后会到哪里)
比如,将 5 5 5加上 1 1 1就要将 6 6 6和 8 8 8都加上1
所以,有什么用呢?
同理, 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)后会回到哪里)
可以发现,如果求 n n n,就永远不会沿绿线到 > n >n >n的数,也不会回到 n n n的儿子( n n n的儿子的值已经加到 n n n中),所以,即可沿路求得 1 − n 1-n 1−n所有数的和
如果换一种方式,可能会清晰一点。
其中,在外面的表示它已经加上了(包括了)里面的值。
例如:求 1 − 7 1-7 1−7的和 = 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+(1−6的和)=7+6[包括5]+(1−4的和)=7+6[包括5]+4[包括1−3]
再放一遍代码:
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 1−n的和,因此只要 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(l−1)=SUM(1,r)−SUM(1,l−1)=SUM(l,r)
返回一个数 :只要 g e t ( x ) − g e t ( x − 1 ) = a [ x ] get(x)-get(x-1)=a[x] get(x)−get(x−1)=a[x]
将一段增加几:类似差分,增加一段后还要减回来。 a d d ( r , x ) ; a d d ( l , − x ) add(r,x);add(l,-x) add(r,x);add(l,−x)
比如,将 [ 2 , 5 ] [2,5] [2,5]增加 5 5 5,结果如上图,其中未被框起来的± 5 5 5是因为包括了要处理的数而被动加减。
思路:因为是大区间获取最值时会与小区间取 m a x max max,所以可以每次更新时只取儿子的 m a x max max,而不用照顾到父亲的值(与求和不同)
要与儿子们取max进行更新,但不能取全部的儿子,比如更新8时要更新7个,会达到 O ( n ) O(n) O(n)。
比如对于更新 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)] n−2x[x<=loglowbit(n)],说人话,就是
for(int t=1;t
所以就简单了。
void add(int i,int x)
{
c[i]=x;
for(int t=1;t
只要找 [ 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;
}