C++树状数组详解

C++树状数组详解

引入

如果给你n个数,然后进行q次询问,每次询问一个区间[x,y]的和,你会怎么做? 
第一种方法:最简单的方法,用数组存起来,每次枚举x-y,ans加起来就可以,时间复杂度O(qn),十分慢。 
第二种方法:或许大多数人会使用前缀和数组:sum[i]=a[1]+a[2]+…+a[i],所以求[x,y]只需要输出sum[y]-sum[x-1]即可,时间复杂度O(n),这是最快的方法之一了。


但是,如果加上一个条件:在q次询问中,有可能会临时使a[m]加上或减去一个数k(我们令这个为update(m,k)操作),也有可能会查询一个区间的和,怎么办呢? 
如果还是用前缀和数组,就不方便了,因为update(m,k)需要更新sum[m]到sum[n]的值,于是时间复杂度又变为了O(qn)。
那么怎么办呢?于是有了树状数组。

树状数组

概念

树状数组,时间复杂度log级别的数据结构,且实现复杂度极小,不论是上面提到的update操作还是求前缀和。 
 
如图,A数组是原始n个数的数组,C数组就是是树状数组(“树状”数组,是指一个普通数组,按树状存储,而不是一种STL中的数据结构)。

实现

观察一下有什么规律。

·       C[1] = A[1]

·       C[2] = C[1] + A[2] = A[1] + A[2]

·       C[3] = A[3]

·       C[4] = C[2] + C[3] +A[4] = A[1] + A[2] + A[3] + A[4]

·       C[5] = A[5]

·       C[6] = C[5] + A[6] = A[5] + A[6]

·       C[7] = A[7]

·       C[8] = C[4] + C[6] + C[7] + A[8] = A[1] + A[2] + A[3] + A[4] + A[5] + A[6]+ A[7] + A[8]

不难发现,好像和二进制很有关系。

但是很难再想下去,事实上是这样的: 
定义lowbit(x)为x二进制下末尾0的个数。 
则含有C[i0i0]的C数组中的位置有: 
i0i0 
i1i1 = i0i0 + lowbit(i0i0) 
i2i2 = i1i1 + lowbit(i1i1) 
i3i3 = i2i2 + lowbit(i2i2) 
… … 
ikik = ik−1ik−1 + lowbit(ik−1ik−1) 
(ik≤nik≤n) 
如果没法理解,写一个循环就懂了:

for(int i=x;i<=n;i+=lowbit(i))

计算lowbit

lowbit(x)=x&-x 
为什么?这里复制了一篇证明(懒得打)

首先明白一个概念,计算机中-i=(i的取反+1),也就是i的补码 
而lowbit,就是求(树状数组中)一个数二进制的1的最低位,例如01100110,lowbit=00000010;再例如01100000,lowbit=00100000。 
所以若一个数(先考虑四位)的二进制为abcd,那么其取反为(1-a)(1-b)(1-c)(1-d),那么其补码为(1-a)(1-b)(1-c)(2-d)。 
如果d为1,什么事都没有-_-|||但我们知道如果d为0,天理不容2Σ( ° △ °|||)︴ 
于是就要进位。如果c也为0,那么1-b又要加1,然后又有可能是1-a……直到碰见一个为补码为0的bit,我们假设这个bit的位置为x 
这个时候可以发现:是不是x之前的bit的补码都与其自身不同?,x之后的补码与其自身一样都是0? 
例如01101000,反码为10010111,补码为10011000,可以看到在原来数正数第五位前,补码的进位因第五位使其不会受到影响,于是0&1=0,; 
但在这个原来数“1”后,所有零的补码都会因加1而进位,导致在这个“1”后所有数都变成0,再加上0&0=0,所以他们运算结果也都是零; 
只有在这个数处,0+1=1,连锁反应停止,所以这个数就被确定啦O(∩_∩)O 
所以and以后只有x这个bit是一……

update操作

当要动态改变一个数时,用刚刚的循环枚举出与它相关的位置,都增加(减少)即可:

void update(int k,int x)
{
    for(int i=k;i<=n;i+=lowbit(i))
        C[i]+=x;}

getsum操作

就是求前缀和,同样的,倒着进行刚刚的循环,累加路上的值即可:

int getsum(int x)
{
    int ans=0;
    for(int i=x;i;i-=lowbit(i))//i要大于0
        ans+=C[i];
    return ans;
}

关于代码风格

树状数组的update和getsum基本是通用的,建议不要自己改函数名,lowbit可以写函数,也可以宏定义:#define lowbit(x) (x&-x)

 

你可能感兴趣的:(其他)