🌲树状数组
🌿 为什么要有树状数组
考虑以下情形
现有一个数组
A[n],讨论以下两种操作的复杂度
- 计算前缀和
A.sum(1, m)- 修改任意一个元素
A[i]
可以有的解法:
- 第一种:对于原始数组,求和操作的复杂度是O(n)O(n)O(n),修改操作的复杂度是O(1)O(1)O(1)。
- 第二种:可以构建一个辅助数组
sum[n],表示前缀和。这样,求和操作的复杂度就变成了O(1)O(1)O(1),而修改操作为O(n)O(n)O(n)。 - 第三种:有没有一种数据结构可以折中一下呢?树状数组:它的求和复杂度为O(logn)O(logn)O(logn),修改复杂度也是O(logn)O(logn)O(logn)。
🌿 树状数组的结构
在原数组A[n]的基础上构建一个辅助数组,记作C[n]。注意:下标从1开始。
🍃 lowBit
- 它表示:一个数用二进制表示时,最低位的1所表示的值。
- 另一重含义:它表示一个数的最大因数,这个因数必须是2的k次幂。
- 计算
lowBit的方法:lowBit = i&(-i)。原理:补码,取反加一。 - 比如:
- 24:二进制表示为
11000,lowBit = 8。 - 12:二进制表示为
1100,lowBit = 4。
- 24:二进制表示为
🍃 C[i]的含义
C[i]表示:数组A[n]中,包括A[i]在内的前方lowBit个元素的总和。
C[i]=∑t=0lowBitA[i−t] C[i] = \sum_{t=0}^{lowBit}A[i-t] C[i]=t=0∑lowBitA[i−t]
C[i]还可以使用数组C[n]中的其他元素来表示:- 对于
i来说:2k=lowBit2^k = lowBit2k=lowBit;i的二进制表示中,末尾有k个0;C[i]表示A[n]中2k2^k2k个元素的和; - 当t<kt<kt<k时,对于i−2ti-2^ti−2t,它的末尾有
t个0;那么C[i−2t]C[i-2^t]C[i−2t]表示A[n]中2t2^t2t个元素的和; - 2k2^k2k可以写成2k=1+∑t=0k−12t2^k=1+\sum_{t=0}^{k-1}2^t2k=1+∑t=0k−12t,所以
C[i]可以用其他的C[n]表示为:
- 对于
C[i]=A[i]+∑t=0k−1C[i−2t],其中2k=lowBit C[i] = A[i] + \sum_{t=0}^{k - 1}C[i-2^t],其中2^k=lowBit C[i]=A[i]+t=0∑k−1C[i−2t],其中2k=lowBit
🍃 父子结点
C[i]的父节点是C[i+lowBit]。C[i]的子节点共有k + 1个,其中2k=lowBit2^k = lowBit2k=lowBit。
🌿 树状数组的操作
🍃 构造
通过C[i]的子节点来构造C[i],复杂度是O(nlogn)O(nlogn)O(nlogn)。
void Init(vector<int> A, vector<int> C) {
for (int i = 1; i < A.size(); ++i) {
int lowBit = i & (-i);
C[i] = A[i];
for (int pf = 1; pf < lowBit; pf <<= 1) {
C[i] += C[i - pf];
}
}
}
🍃 前缀和
复杂度为O(logn)O(logn)O(logn)。
// 计算前缀和,闭区间A[1, m]
int PrefixSum(vector<int> C, int m) {
int sum = 0;
while (m > 0) {
sum += C[m];
m -= (m & (-m));
}
return sum;
}
🍃 区间和
// 计算区间和,闭区间C[a, b]
int IntervalSum(vector<int> C, int a, int b) {
return PrefixSum(C, b) - PrefixSum(C, a - 1);
}
🍃 查询
直接查询A[n]即可,所以复杂度为O(1)O(1)O(1)。
🍃 修改
找到所有的祖先结点即可,复杂度为O(logn)O(logn)O(logn)。
void Update(vector<int>& A, vector<int>& C, int index, int value) {
int delta = value - A[index];
A[index] = value;
// 受影响的结点都增加delta即可
while (index < c.size()) {
c[index] += delta;
index += (i & (-i)); // 定位到父节点
}
}
🌳 拓宽视野
🌾 差分树状数组
优点是:修改区间,查询点
在A[i]的基础上构建差分数组D[i] = A[i] - A[i - 1]。然后在差分数组的基础上构建树状数组C[i],这样可以实现区间更新和单点查询了。

1183

被折叠的 条评论
为什么被折叠?



