树状数组
在求解区间和的时候,使用前缀和数组,可以通过 sum[r] - sum[l-1]的操作O(1)求取sum[l, r] ,但是如果我们需要修改原数组,那么就得遍历一整个数组修改sum[i],O(n);如果用普通数组,反过来更新的时候O(1),但是求区间和的时间复杂度就变为O(n)了。
引入树状数组, 可 以 在 O ( 可以在O( 可以在O( log 2 n \log_{2}{n} log2n )的时间复杂度下更新和求取区间和。
个人理解
原始数组A[1–N],树状数组C[1–N]。
标记为灰色的节点实际已被上层覆盖,不占据空间(灰色的结点的值可以通过其父节点和其余节点的值求出,因此省略)
图片引用至
https://blog.csdn.net/bestsort/article/details/80796531
设节点编号为i,那么该节点维护的值是 [i - lowbit(i) + 1, i] 这个区间的和,C[i]的覆盖长度是lowbit(i)。
C[1] = A[1] //lowbit(1) = 1
C[2] = A[1] + A[2] //lowbit(2) = 2
C[3] = A[3] //lowbit(3) = 1
C[4] = A[1] + A[2] + A[3] + A[4] //lowbit(4) = 4
二进制模式
图片引用至 https://blog.csdn.net/bestsort/article/details/80796531
将n个数字构成了一颗满二叉树(歪的,实际省略了一些结点),奇数号结点必为叶节点,偶数号上升,C[i]的覆盖长度是lowbit(i),即i的叶节点孩子的个数是lowbit(i)。实际将区间分成了多个子区间。
- 更新i的时候,每次从C[i]开始更新,随后对其父节点更新(因为父节点的值是所有子节点的和),直到根节点。在追溯父节点的时候,因为是满二叉树,左右孩子的数量是对称的,都是lowbit(i),则父节点距离结点i的距离(下一个区间的长度)为右孩子数量(即左孩子数量lowbit(i)),加上区间长度后即为下一个区间的右端点也是其父节点。因此寻找父亲的操作为 i+ lowbit(i)。
- 求区间和的时候我们把先前分割的子区间全部加起来就可以。首先加上C[i],然后跳转到上一个区间,因为C[i]代表 [i - lowbit(i) + 1, i] 的和,所以我们跳转到 i - lowbit(i),即上个区间的右端点。
/*==================================================*\
| 树状数组
| INIT: c[]置为0;
| CALL: add(i, v): 将i点的值加v; sum(i): 求[1, i]的和;
\*==================================================*/
#include<bits/stdc++.h>
using namespace std;
#define typev int // type of res
const int N = 1000;
typev c[N]; // index: 1 ~ N
int lowb(int t){
return t & (-t);
}
void add(int i, typev v){
for ( ; i < N; c[i] += v, i += lowb(i));
}
typev sum(int i){
typev s = 0;
for ( ; i > 0; s += c[i], i -= lowb(i));
return s;
}