关于树状数组
前言
前端时间在网上看了很多树状数组的讲稿,但是很多东西都不能一下子理解。试着把关于这个数据结构的东西梳理成博客来加深理解和记忆。
基本概念
树状数组就是把原本的序列换一种方式存储,从而在保留原始值(可查询)的同时,优化更改和询问的时间复杂度。
至于如何构造,先放张(被大家广泛使用的)图好了!

其中,C为我们所使用的树状数组,A为输入的数组,有:
C[1]=A[1], C[3]=A[3], C[5]=A[5], C[7]=A[7];
C[2]=C[1]+A[2],C[6]=C[5]+A[6];
C[4]=C[2]+C[3]+A[4] (=A[1]+A[2]+A[3]+A[4]);
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]).
这样一看是有明显的规律的,但是不好表述,那么我们根据其原理,将下标转化为2进制,就会好理解很多。
| 十进制 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| 二进制 | 1 | 10 | 11 | 100 | 101 | 110 | 111 | 1000 |
|
()2下末尾1 |
1 | 2 | 1 | 4 | 1 | 2 | 1 | 8 |
| 原x个数的和 | 1 | 2 | 1 | 4 | 1 | 2 | 1 | 8 |
因此,树状数组本身已经给我们提供了一种O(log n)的求前缀和的方法。
即,sum(A[1]..A[x])=将x转化为2进制后,各位1表示大小的C[ ]值相加。
那么如何实现这点,这里引入了lowbit函数,即返回一个数二进制最后的“1”所表示的数大小。
常用的两种lowbit函数如下:
int lowbit(int x){ return(x&(-x)); }
int lowbit(int x){ return(x-x&(x-1)); }
void query(int x){ int sum=0; for(;x>0;x-=lowbit(x))sum+=c[lowbit(x)]; }
而我们需要用到树状数组,主要还是为了实时修改更新的复杂度(以及较低的代码复杂度)。因此当我们要对某个结点进行单点修改时,还需要修改在前缀和中包含此节点的一些结点的值。
也即,以修改a[5]为例,我们需要维护c[5],c[6],c[8]。
转化为二进制可能可以看得更加清晰:
c[1001]--->c[1010]---->c[10000]。
对于每一位1,我们先维护它本身,然后x+=lowbit(x);(满足x<=n)
void modify(int x,int delta){ while(x<=n){ c[x]+=delta; x+=lowbit(x); } }
区间查询则可以直接用前缀减代换,即sum[l][r]=sum[r]-sum[l-1]
TBC.

浙公网安备 33010602011771号