树状数组可以看作线段树的变形,不同于线段树可以计算区间和,区间最大/最小值,树状数组一般只能完成以下操作:
给定一个初始值全为0的数列 a 1 . . . a n a_1...a_n a1...an
- 给定 i i i,计算 a 1 + a 2 + . . . + a i a_1+a_2+...+a_i a1+a2+...+ai,即前缀和
- 给定 i i i和 x x x,执行 a i + = x a_i+=x ai+=x。
基于线段树的实现
如果使用线段树执行上述功能,只需要对介绍过的RMQ进行少量的修改,就可以实现,线段树每个节点维护区间的和。
接下来,我们重点关注给定区间始末
s
,
t
s,t
s,t,如何求得
a
s
+
a
s
+
1
+
.
.
.
+
a
t
a_s+a_{s+1}+...+a_t
as+as+1+...+at,对于线段树,我们直接进行区间的迭代查询即可。但是有时这个效率依然是不够的
此时,如果我们维护前缀和
s
u
m
[
i
]
=
a
0
+
.
.
.
+
a
i
sum[i]=a_0+...+a_i
sum[i]=a0+...+ai,那么只需要计算
s
u
m
[
t
]
−
s
u
m
[
s
]
sum[t]-sum[s]
sum[t]−sum[s]即可。在这样的限制下在这样的思路下,对线段树进行改造,会让他发生什么变化呢?我们会发现,线段树所有右儿子的值我们都不在需要进行维护
有了
[
0
−
3
]
[0-3]
[0−3]的前缀和相当于
s
u
m
[
3
]
sum[3]
sum[3]和
0
−
1
0-1
0−1的前缀和相当于
s
u
m
[
1
]
sum[1]
sum[1],我们就不再需要
[
2
−
3
]
[2-3]
[2−3]的前缀和,因为$sum[3]-sum[1]就是2-3的和。基于这种思路得到的数据结构就是BIT(Binary Indexed Tree)。
BIT
所谓BIT就是把线段树中不需要的节点去掉之后,再把剩下的节点对应到数组中。让我们对比每个节点对应的区间的长度和节点编号的二进制表示。以1结尾的1,3,5,7的长度是1,最后有1个0的2,6 的长度是2, 最后有2个0的4的长度是4……这样,编号的二进制表示就能够和区间非常容易地对应起来。利用这个性质,BIT可以通过非常简单的位运算实现。
BIT的求和
计算前
i
i
i项的和需要从
i
i
i开始,不断把当前位置
i
i
i的值加到结果中,并从i中减去
i
i
i的二进制最低非0位对应的幂,直到
i
i
i变成0为止。
i
i
i二进制的最后一个1可以通过
i
&
−
i
i\&-i
i&−i得到。解释一下,计算机内二进制存储形式是补码,正数变负数,补码的话是最后一个非零位不变,这之前的所有位取反,因此正数与他相反数的与就是最后一个非零位的权值。
BIT值的更新
使第
i
i
i项的值增加
x
x
x要从
i
i
i开始,不断把当前位置
i
i
i的值增加
x
x
x,并把
i
i
i的二进制最低非0位对应的幂加到
i
i
i上。也就是说要更新他和他所有的父节点。