1. 什么是树状数组?
-
2. 应用场景?
-
3. 树状数组和线段树的区别?
树状数组可以解决的问题都可以用线段树解决。
- 但树状数组的系数要小很多。
具体使用哪种?优先是有树状数组,树状数组解决不了,使用线段树。
4. 树状数组的优缺点?
修改和查询的复杂度都是O(logN), 系数比线段树要少很多。
-
5. 树状数组原理
黑色数组代表原来的数组(下面用A[i]代替),红色结构代表我们的树状数组(下面用C[i]代替),发现没有,每个位置只有一个方框,令每个位置存的就是子节点的值的和,则有 C[1] = A[1];
- C[2] = A[1] + A[2];
- C[3] = A[3];
- C[4] = A[1] + A[2] + A[3] + A[4];
- C[5] = A[5];
- C[6] = A[5] + A[6];
- C[7] = A[7];
- C[8] = A[1] + A[2] + A[3] + A[4] + A[5] + A[6] + A[7] + A[8];
可以发现,这颗树是有规律的// k为i的二进制中从最低位到高位连续零的长度
例如i = 8(1000)时候,k = 3,可自行验证。
区间求和:
比如我们要找前7项和,那么应该是sum[7] = C[7] + C[6] + C[4];
得到公式:**sum[i] = C[i] + C[i-2^k1] + C[(i-2^k1)-2^k2] + .....;**
树状数组就是一个二进制上面的应用。
i位置上对应的2^k = i & (-i); // 求解2^k,有个专门的名词lowbit。
6. 树状数组代码模板
vector<int> num(n); // 原数组
vector<int> tree(n + 1); // 注意树状树状是从1开始的
int n;
// 求2^k次
int lowbit(int num) {
return (num & (-num));
}
// 求A[1] 到 A[index]的和
int getSum(int index) {
int sum = 0;
for (int i = index; i > 0; i -= lowbit(i)) {
sum += tree[i];
}
return sum;
}
// C[i] = A[i - 2^k + 1] + A[i - 2^k + 2] + ... + A[i];
// 那么如果我们更新某个A[i]的值,则会影响到所有包含有A[i]位置。如果求A[i]包含哪些位置里呢,同理有A[i] 包含于 C[i + 2^k]、C[(i + 2^k) + 2^k]...;
// 在index位置上加个val
void add(int index, int val) {
for (int i = index; i <= n; i += lowbit(i)) {
tree[i] += val;
}
}
7. eg. 单点更新,求区间和
class NumArray {
public:
vector<int> nums; // 原数组
vector<int> tree; // 注意树状树状是从1开始的
int n;
// 求2^k次
int lowbit(int num) {
return (num & (-num));
}
// 求A[1] 到 A[index]的和
int getSum(int index) {
int sum = 0;
for (int i = index; i > 0; i -= lowbit(i)) {
sum += tree[i];
}
return sum;
}
// 在index位置上加个val
void add(int index, int val) {
for (int i = index; i <= n; i += lowbit(i)) {
tree[i] += val;
}
}
NumArray(vector<int>& nums) {
n = nums.size();
tree.resize(n + 1);
this->nums = nums;
for (int i = 0; i < n; i++) {
add(i + 1, nums[i]);
}
}
void update(int index, int val) {
add(index + 1, val - nums[index]);
nums[index] = val;
}
int sumRange(int left, int right) {
return getSum(right + 1) - getSum(left);
}
};