树状数组BIT
BIT
- 树状数组或二叉索引树,全称 Binary Indexed Tree。本质就是用数组模拟树形结构,时间复杂度\(O(log_2n)\)。
- 线段树也能实现树状数组功能。树状数组的复杂度和线段树同级,但代码量更为简洁。
1 问题引入
- 一个长度为n的数组,要求如下两个操作
- 对第i个数加k,复杂度为\(O(1)\)。
- 对区间[1,i]求和,复杂度为\(O(n)\)。
- 当对此修改与查询,最坏为\(O(n^2)\)。当数据量很大时是难以接受的。
- 而树状数组单点更新与区间查询都为\(O(log_2n)\),可以大大降低复杂度。
- 总结一下,当某点经常更新,且要用到前缀和时可以使用树状数组,来降低复杂度。
2 树状数组
- 树状数组是一种可以动态维护序列前缀和的数据结构,它的基本功能是:
- 单点更新 update(i, v): 把序列 i 位置的数加上一个值 v,\(O(log_2n)\)
- 区间查询 query(i): 查询序列[1⋯i] 区间的区间和,即 i 位置的前缀和,\(O(log_2n)\)
- 如图所示,树状数组C存储的是对数组A各个位置的前缀和。
- C[1] = A[1]
- C[2] = A[1] + A[2]
- C[3] = A[3]
- C[4] = A[1] + A[2] + C[3] + A[4]
- ……
- C[7] = A[7]
- C[8] = A[1] + A[2] + C[3] + A[4] + A[5] + A[6] + A[7] + A[8]
2.1 lowbit()操作
- 树状数组的前缀和,被分成了不同区间的和。与动态规划中的存储之前计算过的值来降低复杂度一样,树状数组也是用之前的区间值来累加,避免重复计算。
// 求最低位二进制为1的数
// 10100 -> 00100 = 4
// 01110 -> 00010 = 2
// 原码&
double lowbit(double x){
return x&(-x);
}
- 正数的原码、反码、补码相同,负数等于反码+1。通过
x&(-x)可以只保留最低位为1的数。- 如原码
10100变反码01011,反码01011再加1等于01100,最终相与得00100。
- 如原码
2.2 query()操作
- 树状数组C[X]等于
[X-lowbit(X)+1,X]中所有值的和。 - 当要求X的前缀和时,query(X)= C[X] + C[X-lowbit(X)]+...
- 直到 X = 0
int query(int x)
{
int ret = 0;
while (x) {
ret += C[x];
x -= lowbit(x);
}
return ret;
}
2.3 update() 操作
- 当对该点更新时,需要对其父结点同步更新
- 如当A[6]更新时,需要更新后面的C[6]、C[8]等。
void update(int x) {
while (x <= n) {
++C[x];
x += lowbit(x);
}
}
BIT.cpp
class BIT {
private:
vector<int> tree;
int n;
public:
BIT(int _n): n(_n), tree(_n + 1) {}
static int lowbit(int x) {
return x & (-x);
}
int query(int x) {
int ret = 0;
while (x) {
ret += tree[x];
x -= lowbit(x);
}
return ret;
}
void update(int x,int v=1) {
while (x <= n) {
tree[x] += v;
x += lowbit(x);
}
}
};
3 应用
- 树状数组与逆序对
- ...
参考资料:
https://www.bilibili.com/video/BV1xq4y1i7et
https://blog.csdn.net/qq_40941722/article/details/104406126
https://leetcode-cn.com/problems/shu-zu-zhong-de-ni-xu-dui-lcof/

浙公网安备 33010602011771号