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位置上加个valvoid 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位置上加个valvoid 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);}};
