为什么要使用树状数组
事实上,树状数组的所有操作均可以使用线段树完成。然而,打个比方,你可以用高精度写完只要你算 \(A + B\) 且结果比较小的题目,但是你不会去写高精度,因为那太麻烦了,而且时间和空间效率都不是很高。而树状数组相对于线段树的优势也就是在时间和空间的优势上,掌握了树状数组与没掌握,在做一些题目时就是 \(AC\) 与 \(TLE\) 的区别了。
树状数组的原理
以单点修改求区间和为例,区间树状数组事实上可以看成是一种特殊的前缀和数组,他的过程是这样的(假设原数组为 \([1,2,3,4,5,6,7,8,9]\) ):
- 将数组中的偶数位分离出来 ( \([2,4,6,8]\) )
- 每个偶数位的数加上与它相邻的左边一位的数,得到一个新的序列 ( \([3,7,11,15][2,4,6,8]\) )
- 对新的序列重新进行步骤一至步骤二,直到不能进行操作为止 ( \([3,7,11,15][2,4,6,8]\to [10,26][4,8]\to [34][8]\) )
- 将原来的奇数位混插回去,得到树状数组 ( \([1,3,3,10,5,11,7,36,9]\) )
注:\([a,b][c,d]\) 表示序列中有两个元素 \(a,b\) ,他们在原数组中对应的位置是 \(c,d\) 。
如果记原数组为 \(A\) ,树状数组为 \(C\) ,从上面的过程我们可以看出(为了方便表述,我们将下标表示为二进制形式):
\(C_{0001} = A_{0001}\ (C_1 = A_{1})\)
\(C_{0010} = A_{0010} + A_{0001}\ (C_{2} = A_1 + A_2)\)
\(C_{0011} = A_{0011}\ (C_{3} = A_3)\)
\(C_{0100} = A_{0100} + A_{0011} + A_{0010} + A_{0001}\ (C_{4} = \sum\limits_{i = 1}^4 A_i)\)
\(C_{0101} = A_{0101}\ (C_{5} = A_5)\)
\(C_{0110} = A_{0110} + A_{0101}\ (C_{6} = A_5 + A_6)\)
\(C_{0111} = A_{0111}\ (C_7 = A_7)\)
\(C_{1000} = C_{1000} + C_{0111} + C_{0110} + C_{0101} + C_{0100} + C_{0011} + C_{0010} + C_{0001}\ (C_8 = \sum\limits_{i = 1}^8 A_i)\)
\(C_{1001} = A_{1001}\ (C_9 = A_9)\)
我们可以发现,\(C_i = \sum\limits_{i = l}^r A_i\),其中 \(r = i\) ,\(l\) 是 \(i\) 去掉最低位的 \(1\) 再在最低位补 \(1\) 后得到的数,即 \(l = (i\ \&\ (i\ \hat\ \ (i - 1))) + 1\) ,我们给了 \(i\ \&\ (i\ \hat\ \ (i - 1))\) 另外一个名称,即 \(lowbit(i)\) ,它还可以表示为 \(i\ \&\ (-i)\) ,至于为什么,就和计算机中负整数补码有关了,网络上有很多相关资料,在此不做赘述。
再放一张图以帮助理解,蓝色为 \(A\) ,橙色为 \(C\):
所以我们现在就理清了树状数组和原数组的关系:\(C_i = \sum\limits_{j = lowbit(i) + 1}^i A_j\)
我们还可以发现 \(lowbit(i)\) 表示的就是 \(i\) 的二进制表示只保留最低位的 \(1\) 的数。
updating……