树状数组
基本原理
树状数组(Binary Indexed Tree,简称 BIT)是一种高效的数据结构,它可以在O(log n)的时间复杂度下实现对数组的单点更新和区间求和操作。
前置知识
lowbit(int x)
函数:计算x
的最低位的 1 及其后面的 0 组成的数,例如lowbit(6)
(二进制为110
)等于2
(二进制为10
)。- 在树状数组中,
i
元素的右父节点为i+lowbit(i)
,左父节点为i-lowbit(i)
核心操作
1. 单点更新
当更新数组中某个元素的值时,需要更新树状数组中与之相关的节点。对于更新操作 update(int index, int val)
,从 index
开始,不断向上更新父节点,更新的规则是将 index
加上 index & -index
,直到超出数组范围。
// 单点更新操作,将位置 index 的元素值增加 val
void update(int i, int val) {
while (i <= n) {
tree[i] += val;
i += lowbit(i);
}
}
void update(int i, int val)
: 定义一个公共成员函数update
,用于在树状数组的指定位置i
增加一个值val
。while (i <= n)
: 开始一个循环,只要i
小于或等于n
,就继续执行循环。tree[i] += val;
: 将值val
加到向量tree
的第i
个位置。i += lowbit(i);
: 更新索引i
,使其指向下一个需要更新的节点。lowbit(i)
返回i
的最低有效位,这个操作确保了树状数组的更新操作能够正确地影响所有相关的节点。
2. 区间求和
对于查询区间 [1, index]
的和,从 index
开始,不断向下查找子节点,将结果累加,查找的规则是将 index
减去 index & -index
,直到 index
为 0。
// 区间求和操作,求 [1, i] 的和
int query(int i) {
int sum = 0;
while (i > 0) {
sum += tree[i];
i -= lowbit(i);
}
return sum;
}
对于查询区间 [l, r]
的和,我们只需要分别求出区间[1, l]
和区间[1, r]
进行相减即可
// 区间求和操作,求 [l, r] 的和
int query(int l, int r) {
return query(r) - query(l - 1);
}
代码实现
#include <iostream>
#include <vector>
using namespace std;
class BinaryIndexedTree {
private:
vector<int> tree;
int n;
int lowbit(int x) {
return x & -x;
}
public:
BinaryIndexedTree(int size) {
n = size;
tree.resize(n + 1,0);
}
// 单点更新操作,将位置 index 的元素值增加 val
void update(int i, int val) {
while (i <= n) {
tree[i] += val;
i += lowbit(i);
}
}
// 区间求和操作,求 [1, i] 的和
int query(int i) {
int sum = 0;
while (i > 0) {
sum += tree[i];
i -= lowbit(i);
}
return sum;
}
// 区间求和操作,求 [l, r] 的和
int query(int l, int r) {
return query(r) - query(l - 1);
}
};
int main() {
BinaryIndexedTree bit(10);
bit.update(1, 1);
bit.update(2, 2);
bit.update(3, 3);
bit.update(4, 4);
bit.update(5, 5);
bit.update(6, 6);
bit.update(7, 7);
bit.update(8, 8);
bit.update(9, 9);
bit.update(10, 10);
cout << bit.query(1, 10) << endl; // 55
return 0;
}
代码解释
BinaryIndexedTree
类:bit
是存储树状数组的向量,n
是数组的长度。lowbit(int x)
函数:计算x
的最低位的 1 及其后面的 0 组成的数,例如lowbit(6)
(二进制为110
)等于2
(二进制为10
)。update(int index, int val)
函数:- 从
index
开始,不断更新bit[index]
的值。 index += lowbit(index);
用于找到下一个需要更新的节点,直到index
超出数组范围。
- 从
query(int index)
函数:- 从
index
开始,将bit[index]
的值累加到sum
中。 index -= lowbit(index);
用于找到下一个需要累加的节点,直到index
为 0。
- 从
应用场景
- 动态更新和查询:当需要对数组进行频繁的单点更新和区间求和操作时,树状数组比普通的数组实现更高效,例如在数据频繁更新的统计系统中,如在线游戏中的玩家得分更新和排名统计。
- 求逆序对:可以将数组元素离散化,然后使用树状数组统计每个元素之前比它大(或小)的元素的数量,从而计算逆序对的数量。
使用树状数组可以有效地解决一些动态更新和区间求和的问题,提高程序的运行效率,尤其是在数据规模较大时,它能显著降低时间复杂度。
注意事项
- 树状数组的索引通常从 1 开始,因此在存储和操作时需要注意数组元素的偏移。
- 对于不同的问题,需要根据具体情况对树状数组的更新和查询操作进行灵活运用,有时可能需要对原数组进行一些预处理,如离散化等。