代码量和线段树相比少了好多。
树状数组所能解决的问题使用线段树均能解决。
主要用于求解快速前缀和——在O(logn)的时间内完成单点修改与区间查询(前缀和)。
树状数组理论上只能完成单点修改与区间查询,其他的问题均是转换为这一问题去处理。如(区间修改、单点修改 | 区间修改、区间查询)。
括号的内容太复杂了,y总说蓝桥杯不考…
--2022-3-31 17:28
1.存储组织
树状数组与原数组的关系可以用下图表示(图片来自博客):

黑色数组代表原数组(a)。红色结构则代表树状数组©,存储的值是其全部子结点的值之和。可以看出:
c[1] = a[1]
c[2] = a[1] + a[2]
c[3] = a[3]
c[4] = a[1] + a[2] + a[3] + a[4]
…
c[8] = a[1] + a[2] + … + a[8]
我们定义k为索引i二进制表示中末尾连续0的个数。那么c[i]表示区间(a[i−2k],a[i]]的所有元素和。
2.k的求解
为了建立树状数组,我们首先要求解出2k。这里利用了负数在计算机中的存储特性来求解。
负数在计算机中以补码存储。对于运算x&(−x):
- 当x=0时,0&0结果仍为0.
- 当x为奇数时,最后一位二进制数为1,取反加1后最后一位仍然为1,因此x与−x除最后一位相同外,其余位均相反,即x&(−x)=1。正好满足了20=1.
- 当x为偶数时,且为2的m次方时,此时x最后有m位0,当x取反加一后,末尾正好有m位0,其余位均为1,因此x&(−x)=x
- 当x为偶数,且不为2的m次方时,可以将x表示为一个奇数左移k位,即x=y×(2k)。即x的二进制表示下,最右边有连续k个0,第k+1位为1。取反加一后,最右侧仍为k个0, 第k+1位为1,其余位均与原数不同,因此x&(−x)=2k。
故,x&(−x)可以求得2k的值。
3.索引关系
为了应付蓝桥杯,这里选择记住,特别是第二点:
- 树状数组索引从1开始。
- 索引为x的节点,其父节点索引为x+lowbit(x)=x+(x&(−x))。
4.相关代码
我们使用a数组表示原数组,使用tree数组表示树状数组(索引从1至n)
4.1建树
我们将树状数组初始化为全0,那么建树的过程可以看作是为全零数组的每一个位置加上a[i]的过程。
| for(int i = 1; i <= n; i++) add(i, a[i]);
|
4.2lowbit
1 2 3
| static int lowbit(int x){ return x & (-x); }
|
4.3查询query
query(x)是要求出区间[1,x]的和。由于tree[x]存储了a[x−lowbit(x)+1]、a[x−lowbit(x)+2]、...、a[x]的和,因此只要将x更新为x−lowbit(x),并重新执行query(x−lowbit(x)),如此递归直至x=0即可。
1 2 3 4 5 6 7
| static int query(int x){ int sum = 0; for(int i = x; i > 0; i = i - lowbit(i)){ sum += tree[i]; } return sum; }
|
4.4修改add
add(x,y)是将a[x]的值加上y。在3.索引关系中提到,x的父节点索引为x+lowbit(x),因此通过不断的将x更新为x+lowbit(x),即可修改全部包含x项的节点信息。
1 2 3
| static void add(int x, int y){ for(int i = x; i <= n; i = i + lowbit(i)) tree[i] += y; }
|