树状数组(Binary Indexed Tree),又称Fenwick树,是一种用于高效处理前缀和的数据结构。它的核心思想是通过一系列的位运算来优化求和过程,从而在O(log n)的时间复杂度内完成单点更新和区间查询等操作。
树状数组的基本思想是将原始数组A[1...n]映射到一个新的数组C[1...n],使得对于任意的前缀和S[i] = A[1] + A[2] + ... + A[i],都可以通过C数组的某些元素的加减运算得到。具体地,对于A数组中的每一个元素A[i],我们将其贡献拆分成多个部分,并分别加到C数组的对应位置上。
树状数组的基本操作:
- 单点更新操作是指在A数组的某个位置i上增加或减少一个值val,并更新C数组以保证前缀和的正确性。这个操作的时间复杂度为O(log n)。示例代码如下。
cpp
void update(int i, int val) {
while (i <= n) {
C[i] += val;
i += i & (-i);
}
}
- 前缀和查询操作是指求A数组中前i个元素的和S[i]。由于C数组是经过特殊构造的,我们可以通过对C数组中的某些元素进行加减运算来快速得到S[i]。这个操作的时间复杂度也为O(log n)。示例代码如下。
cpp
int query(int i) {
int sum = 0;
while (i > 0) {
sum += C[i];
i -= i & (-i);
}
return sum;
}
下面是一个使用C++编写的树状数组实例,它演示了如何更新序列中某个位置的值及查询序列中某个位置的前缀和(即该位置及之前所有位置的值之和)。代码如下。
cpp
#include <vector>
using namespace std;
class FenwickTree {
private:
vector<int> bit;
int lowbit(int x) {
return x & (-x);
}
public:
FenwickTree(int n) : bit(n + 1, 0) {}
void update(int index, int val) {
while (index < bit.size()) {
bit[index] += val;
index += lowbit(index);
}
}
int query(int index) {
int sum = 0;
while (index > 0) {
sum += bit[index];
index -= lowbit(index);
}
return sum;
}
};
int main() {
int n, m;
cin >> n >> m; // 输入序列长度和操作次数
FenwickTree ft(n);
vector<int> a(n + 1);
// 初始化序列和树状数组
for (int i = 1; i <= n; ++i) {
cin >> a[i];
ft.update(i, a[i]);
}
// 执行m次操作
for (int i = 0; i < m; ++i) {
int op, p, v;
cin >> op;
if (op == 1) { // 更新操作
cin >> p >> v;
ft.update(p, v - a[p]); // 更新差值,避免重复加值
a[p] = v; // 更新原序列中的值
} else if (op == 2) { // 查询前缀和操作
cin >> p;
cout << ft.query(p) << endl;
}
}
return 0;
}
现在,我们来测试一下这个程序。假设输入如下:
5 2
1 2 3 4 5
1 2 10
2 5
输出结果如下。
通过这个示例,我们可以看到树状数组在解决实际问题中的应用。它通过高效的前缀和计算,使得在更新和查询操作中都能保持较低的时间复杂度。当然,树状数组并不是解决所有问题的银弹,但在某些特定场景下,它可以提供非常优秀的性能。