核心
树状数组是一种用来解决区间单点更新,区间求和问题的简单数据结构。其核心思想在于二进制特殊性质。
1 | int lowbit(int x) |
lowbit
lowbit 函数的作用是取得 x 最右边一个 1 对应的数值。 如果 x 的二进制可以表示为 00010010,那么 lowbit 的结果为 2。怎么做到的呢?-x 的补码是 x 按位取反加 1,0010 变成 1101 +1 = 1110 ,-x 从右数第一个 1 的位置对应 x 从右数第一个 x 的位置,其他均取反。
如此便可以取得所要得到的数。
得到了这个数又有什么用呢。可以看下表
普通数组 a | 树状数组 c | 解释 | 二进制 |
---|---|---|---|
1 | 1 | a[1] | 0001 |
2 | 3 | a[1]+a[2] | 0010 |
3 | 3 | a[3] | 0011 |
4 | 10 | a[1]+a[2]+a[3]+a[4] | 0100 |
5 | 5 | a[5] | 0101 |
6 | 11 | a[5]+a[6] | 0110 |
7 | 7 | a[7] | 0111 |
8 | 36 | a[1]+…+a[8] | 1000 |
9 | 9 | a[9] | 1001 |
对于一个位置,看它的二进制,这个位置保存的元素个数就是其二进制最右边 1 代表的大小,保存从去掉最右边 1 到加上最右边 1 的位置之间的数据(左开右闭)。
这样表述很不清楚,直接距离,看 c[6]位置,6 的二进制是 0110,去掉最右边 1 是 0100,即十进制的 4,它保存的是(4,6]之间的数据。
那么对于求和操作,就变成了不断找右边的 1 的过程。
对于更新操作,我们要同时更新其之后的父节点。
当我们修改 a[i]的值时,可以从 c[i]往根节点一路上溯,调整这条路上的所有 C[]即可,对于节点 i,父节点下标 p=i+lowbit(i)
树状数组多用于求逆序对,原理是将序列按顺序插入树状数组时,每次统计比当前数小的元素数量 sum,则与当前数构成逆序的对数有 i-sum
可以离散化数据,我们不关心具体元素的数值,只关心其对应关系。
代码实现
1 |
|
扩展到二维
问题:一个由数字构成的大矩阵,能进行两种操作
- 对矩阵里的某个数加上一个整数(可正可负)
- 查询某个子矩阵里所有数字的和,要求对每次查询,输出结果。
一维树状数组很容易扩展到二维,在二维情况下:数组 A[][]的树状数组定义为:
其相应的更新与求和操作也很容易类比出来,这里就不做说明了。