树状数组(Binary Indexed Tree,简称 BIT 或 Fenwick Tree)是一种可以高效解决前缀和问题的数据结构。它能在对数时间复杂度内完成单点更新和查询前缀和的操作。树状数组通过一种巧妙的方式,将数组元素的值分布在不同的位置上,使得查询和更新操作都能够在 O(log n) 的时间复杂度内完成。
树状数组的基本思想
树状数组将数组分为若干个“树状”的组,每个组内的元素更新会影响到多个父节点,查询前缀和时则只需要考虑这些父节点的值。具体来说,树状数组的每个节点 C[i] 维护的是原数组 A 中下标从 A[i-lowbit(i)+1] 到 A[i] 的所有元素的和。
其中,lowbit(i)
表示 i 的二进制表示中最低位的 1 及其后面的 0 所组成的数。例如,对于 i = 10(二进制 1010),lowbit(10) = 2(二进制 10)。
基本操作
1. 单点更新
要将原数组 A 中的某个元素 A[i] 更新为 v,需要更新树状数组中从 C[i] 开始,到所有包含 C[i] 的父节点为止的所有节点。
cpp复制代码
void update(int i, int delta) { | |
while (i < N) { // N 是数组的大小 | |
C[i] += delta; | |
i += lowbit(i); | |
} | |
} |
2. 查询前缀和
要查询原数组 A 中从 A[1] 到 A[i] 的所有元素的和,需要累加树状数组中从 C[i] 开始,到所有不包含 C[i] 的祖先节点为止的所有节点的值。
cpp复制代码
int query(int i) { | |
int sum = 0; | |
while (i > 0) { | |
sum += C[i]; | |
i -= lowbit(i); | |
} | |
return sum; | |
} |
lowbit 函数
lowbit 函数的作用是获取一个数的二进制表示中最低位的 1 及其后面的 0 所组成的数。可以通过位运算实现:
cpp复制代码
int lowbit(int x) { | |
return x & -x; | |
} |
示例
假设有一个数组 A = [1, 2, 3, 4, 5],则对应的树状数组 C 可以通过以下方式构建:
- C[1] = A[1] = 1
- C[2] = A[1] + A[2] = 1 + 2 = 3
- C[3] = A[3] = 3
- C[4] = A[4] = 4
- C[5] = A[5] = 5
- C[6] = A[1] + A[2] + A[3] + A[4] + A[5] = 15
- C[7] 及以上不需要(因为数组大小只有 5)
注意:在实际应用中,树状数组的索引通常从 1 开始,而不是从 0 开始,以避免处理边界情况。同时,为了方便计算,树状数组的大小通常设为比原数组大 1 的 2 的幂次方。
应用场景
树状数组常常用于解决需要频繁查询前缀和或进行单点更新的问题,如求部分和、区间和、动态数组查询等。由于它的高效性和易实现性,树状数组在编程竞赛和算法研究中有着广泛的应用。