1. 什么是树状数组
树状数组是一个查询和修改复杂度都为 log( n ) 的数据结构。主要用于:数组的单点修改、区间求和。
在使用前缀和求区间和的算法中,如果可以做到在 O( 1 ) 的时间复杂度内查询任意的区间和,但是如果要修改其中一个点的值,那么需要修改一遍前缀和数组,修改的时间复杂度是O(n)。
(1)树状数组拆分原理
正如所有的整数都可以表示成 2 的幂和,我们也可以把一串序列子序列的和。采用这个想法,我们可以将一个前缀和划分成多个子序列的和,而划分的方法与数的 2 的幂和具有极其相似的方式。
比如:整数 21 的对应的 2 进制是 10101,对应运算 = ,因此一个长度为 21 的数组,可以拆分为三段:
子序列的个数是其二进制表示中 1 的个数。
根据该理论,一个长度为 len 的区间 [ 1,len ],可以采用上述思想划分 log( len ) 个子区间。
从右往左看,每个区间的大小其实是 len 对应的二进制的最后一个 1 的 2 的次幂。
(2)如何求整数 x 对应的最后一个 1 往后的值?
lowbit( x ) :代表 x 对应 2 进制的从最后一位 1 开始向后构成的值。
例如:
lowbit( 10 ):返回 10.
lowbit( 40 ):返回 1000.
计算方法:
假设 x = 270 对应的二进制为:1011,1000.
先将 x 取反 = 0100,0111,再+1 = 0100,1000,此时发现这个值和原来的 x 对应的 2 进制,最后一个 1 向后的值一致,前面的值正好相反,只要那这两个值做 & 运算,结果就是 lowbit( x ) 的值。
在计算机中,负数是以补码的形式存储的,补码就是数值位取反+1的过程。
因此:lowbit( x ) = x & ( ~x + 1 ) = x & -x;
(3)树状数组的划分方法
以 a[ x ] 结尾的区间,区间的长度为 x 的最后一个 1 所对应的 2 的次幂。
A. 以 a[ x ] 结尾的区间,区间长度为 x 的最后一个 1 所对应的 2 的次幂。
B. 设定 c[ x ] 表示:a 数组中 ,长度为 lowbit( x ) 的区间和,所管理的区间为[ x - lowbit( x ) +1,x]。
C.该结构的特点:
a、除树根外,结点 x 的父节点 = x + lowbit( x );
b、长度为 n 的数组 a,所构建的树的深度 = log( n );
注意:树状数组的下标一般从 1 开始,因为如果从 0 开始,lowbit( 0 ) = 0,容易出现死循环。
(4)树状数组修改元素
//在数组 a 的第 x 数上增加数字 k
void update(int x,int k){
for(int i=x;i<=n;i=i+lowbit(i)) c[x]+=k;
}
(5)树状数组求前缀和