导入
前缀和 相信大家都知道,它可以用来求区间和。
可是前缀和不怎么灵活,如果要对某个点进行修改,那就需要更新前缀和数组的所有下标不小于修改位置的元素的值,算法复杂度最高可达 O ( n ) O(n) O(n),速度十分缓慢,如果碰到大量修改,会直接歇菜。
为了加快单点修改的速度,树状数组就诞生了!
什么是树状数组
树状数组或二叉索引树(英语:Binary Indexed Tree),又以其发明者命名为Fenwick树,最早由Peter M. Fenwick于1994年以A New Data Structure for Cumulative Frequency Tables为题发表在SOFTWARE PRACTICE AND EXPERIENCE。其初衷是解决数据压缩里的累积频率(Cumulative Frequency)的计算问题(当我没说),现多用于高效计算数列的前缀和, 区间和。
以上内容来自网络。
如果你看完了上一段,那么恭喜你!浪费了10秒钟。
树状数组支持以下两种操作,
- 单点修改。
- 区间和查询。
两种操作的时间复杂度都为 O ( l o g n ) O(log\space n) O(log n)。
树状数组的构造
树状数组的思想大概是这样的,我们可以为数组维护多个区间,在查询和修改时,对这几个包含该点或区间的区间进行修改和查询。
可这些区间也不能随便设置,不然在进行查询修改时,如果包含区间的区间过多,那也会导致速度缓慢。
好了,不扯这些废话了,先来看看它长什么样吧。
设一个长度为 n n n 的树状数组 c i c_i ci,那它的元素维护的区间是这样的。
其中 c i c_i ci 就等于 a a a 数组 [ i − l o w b i t ( i ) + 1 , i ] [i-lowbit(i)+1,i] [i−lowbit(i)+1,i] 的区间和。
l o w b i t ( i ) lowbit(i) lowbit(i) 是什么呢? l o w b i t ( i ) lowbit(i) lowbit(i) 指的是 i i i 在二进制下最后一个的为 1 1 1 的哪一位所代表的值。比如当 i = 10 i=10 i=10,即 ( 1010 ) 2 (1010)_2 (1010)2 时,最后一个为 1 1 1 的二进制位为从右往左第二位,所以 l o w b i t ( i ) = ( 10 ) 2 = 2 lowbit(i)=(10)_2=2 lowbit(i)=(10)2=2。
lowbit的实现方法
l o w b i t ( x ) lowbit(x) lowbit(x) 的实现方法如下:
int Lowbit(int x){
return x&(-x);
//十分简洁,算法复杂度为 O(1)
}
为什么是 x&(-x)
呢?
大家应该都知道二进制的 原码、反码、补码。当 x x x 为负数时,他的二进制就是用补码来表示。即将 x x x 包括符号位取反后加 1 1 1。
而将 x x x 变成负数后,二进制在运算时就会变成补码。因为 l o w b i t ( x ) lowbit(x) lowbit(x) 是最后一个为 1 1 1 的二进制位,所以它后面所有的二进制位都为 0 0 0,取反后自然为 1 1 1, l o w b i t ( x ) lowbit(x) lowbit(x) 的那一位则会变成 0 0 0,加 1 1 1 后, l o w b i t ( x ) lowbit(x) lowbit(x) 后面的二进制位会一直进位到 l o w b i t ( x ) lowbit(x) lowbit(x) 这一位,并全部变为 0 0 0。所以 l o w b i t ( x ) lowbit(x) lowbit(x) 的后面的二进制位进行按位与后都为 0 0 0。而 l o w b i t