#define lowbit(x) ((x) & - (x))
int tree[N];
void update(int x, int d) { //修改元素a[x], a= a[x] + d
while(x <= N) {
tree[x] += d;
x += lowbit(x);
}
}
int sum(int x) { //返前缀和ans = a[1] + a[2] +... + a[x]
int ans = 0;
while(x > 0){
ans += tree[x];
x -= lowbit(x);
}
return ans;
}
这个是一维树状数组的代码,背下来即可。
#define lowbit(x) ((x) & - (x))
int tree[N];
void update(int x, int d) { //修改元素a[x], a= a[x] + d
while(x <= N) {
tree[x] += d;
x += lowbit(x);
}
}
int sum(int x) { //返前缀和ans = a[1] + a[2] +... + a[x]
int ans = 0;
while(x > 0){
ans += tree[x];
x -= lowbit(x);
}
return ans;
}
这是二维树状数组的代码
这里我们直接给出一维树状数组的例题和解析
#include <bits/stdc++.h>
using namespace std;
const int N = 1000;
#define lowbit(x) ((x) & - (x))
int tree[N]={0};
void update(int x, int d) { //修改元素a[x], a[x] = a[x] + d
while(x <= N) {
tree[x] += d;
x += lowbit(x);
}
}
int sum(int x) { //返回前缀和sum = a[1] + a[2] +... + a[x]
int ans = 0;
while(x > 0){
ans += tree[x];
x -= lowbit(x);
}
return ans;
}
//以上是树状数组的代码
int a[11]={0,4,5,6,7,8,9,10,11,12,13}; //注意:a[0]不用
int main (){
//计算 tree[]数组
for(int i=1;i<=10;i++)
update(i,a[i]);
//查询区间和,例如查询[5,8]
cout << "old: [5,8]="<<sum(8)-sum(4)<<endl;
//模拟一次修改: a[5] = a[5]+100
update(5,100);
//重新查询区间和[5,8]
cout << "new: [5,8]="<<sum(8)-sum(4);
return 0;
}
树状数组的原理
以这个图为例子,右图圆圈中标记有数字的结点,存储的是称为树状数组的 tree[]。一个结点上的 tree[]的值,就是它树下的直连的子结点的和。例如:
tree[1]=a1
tree[2]=tree[1]+a2;
tree[3]=a3;
tree[4]=tree[2]+tree[3]+a4;
利用tree[],高效的完成查询前缀和和维护
前缀和,求前缀和sum
sum[8]=tree[8];
sum[7]=tree[7]+tree[6]+tree[4];
右图虚线箭头的就是计算sum的过程
维护:当一个元素a发生改变的时候,比如更新了a3那么只需要修改覆盖了a3的tree[3],tree[4],tree[8]
-
查询的过程,是每次
去掉二进制的最后的1
sum(7) = tree[7] + tree[6] + tree[4]
,步骤是:
-
7 的二进制是 111,去掉最后的 1,得 110,即 tree[6]
-
去掉 6的二进制 110的最后一个 1,得 100,即 tree[4];
-
4 的二进制是 100,去掉 1 之后就没有了。
-
-
维护的过程,是每次
在二进制的最后的1上加1
。例如更新了a3
,需要修改tree[3],tree[4],tree[8]
等等,步骤是:
-
3 的二进制是 11,在最后的 1 上加上 1 得 100 ,即 4 ,修改 tree[4];
-
4 的二进制是 100,在最后的 1 上加 1,得 1000,即 8,修改 tree[8];
-
继续修改 tree[16]、tree[32]⋯ 等等。
-
你会吗,我不会,记住就完事了好吗,好的
-