概念:
定义:
树状数组是一种结合了树的思想,常用来处理前缀问题(如前缀最大/最小值,前缀和)的一种数据结构,区查和单修时间复杂度都为


(右图括号中的值表示原序列中该数的值,未打括号的表示下标)
这一张图可以很好地展示一维树状数组的结构及其维护信息的原理。令该树状数组为

结合图观察,通过树状数组求前缀和就是将某个区间拆成树状数组里的若干段的规律就是将该数进行二进制拆分,每一次取出
例如
lowbit:
那么如何每次准确地取出
在计算机中,负数用补码表示,补码等于正数用二进制表示后每一位取反后再加一。如
int lowbit(int i){
return i & -i;
}
还可以采用异或实现
一维树状数组:
单修区查:
单修:
仍然结合图像。


假如我们对原序列中第十一位进行修改,那么树状数组中,包含原序列第十一位的数的信息的所有结点都需要改变,这就像从第十一位拉一条水平方向的横线(如右图),被这条横线所切到的所有结点的值都要被改变(红圆标注)。在这种情况下,树状数组中下标为
观察三数,转化为二进制后分别为
实现如下:
void add(int x,int y){//在下标为x的地方加上y
for(int i = x; i <= n; i += lowbit(i)){
bit[i] += y;
}
}
区查:
按照介绍
int query(int x){
int ans = 0;
for(int i = x; i > 0; i -= lowbit(i)){
ans += bit[i];
}
return ans;
}
求得前缀和,进行区间和查询也就易如反掌了。
区修单查:
区修:
对于树状数组的区间修改,要运用差分的思想,一个数组的差分数组和它的前缀和是互逆的。
a[6] = {0, 1, 2, 3, 4, 5};
cf[6] = {0, 1, 1, 1, 1, 1}; //cf[i] = a[i] - a[i - 1] 差分
qzh[6] = {0, 1, 3, 6, 10, 15}; //qzh[i] = qzh[i - 1] + a[i] 前缀和
cf_qzh[6] = {0, 1, 2, 3, 4, 5}; //差分数组的前缀和数组
qzh_cf[6] = {0, 1, 2, 3, 4, 5}; //前缀和数组的差分数组
容易发现,差分数组的前缀和就是原数组的数,前缀和的差分数组就是原数组。
而差分数组的区间修改是将
对于这道题,我们不再在原数组上建树状数组了,改在差分数组上建树状数组。
单查:
每次区间修改,就对
void change(int l,int r,int x){//将l到r的数加上x
add(l,x);
add(r + 1,-x);
}
区修区查:
区修:
同上。
区查:
这里就需要稍微推一下柿子了。设原序列为
这个柿子的结果就是区间和。展开得到:
化简后得:
因此只需要维护两个前缀和,分别是
二维树状数组:
单修区查:
其实跟一维的大同小异,这里就放代码了:
void add(int x,int y,int z){
for(int i = x; i <= n; i += lobit(i)){
for(int j = y; j <= m; j += lowbit(j)){
bit[i][j] += z;
}
}
}
int query(int x,int y){//左上端点为(1,1),右下端点为(x,y)的矩阵元素之和
int ans = 0;
for(int i = x; i > 0; i -= lowbit(i)){
for(int j = y; j > 0; j -= lowbit(j)){
ans += bit[i][j];
}
return ans;
}
}
在进行区间矩阵求和时,还要运用到容斥定理,如下图(找别人薅的

大正方形减去两个小的长方形,加上小正方形即为红框区间内的答案。
int tot(int x1,int y1,int x2,int y2){
int ans = 0;
ans += add(x2,y2);
ans -= add(x2,y1 - 1);
ans -= add(x1 - 1,y2);
ans += add(x1 - 1,y1 - 1);
return ans;
}
区修单查:
区修:
一位的区修依赖差分数组,那么二维的区修就依赖差分矩阵。
有如下矩阵
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
想进行区域修改,得到
0 0 0 0 0
0 x x x 0
0 x x x 0
0 x x x 0
0 0 0 0 0
那么在差分矩阵里,就应该是这样的
0 0 0 0 0
0 x 0 0 -x
0 0 0 0 0
0 0 0 0 0
0 -x 0 0 x
代码如下:
void change(int x1,int y1,int x2,int y2,int x){
add(x1,y1,x);
add(x1,y2 + 1,-x);
add(x2 + 1,y1,-x);
add(x2 + 1,y2 + 1,x);
}
单查:
如一维一样,统计前缀和即可。
区修区查:
区修:
同上。
区查:
令
类比一维树状数组,发现
所以可以化简,得到:
显而易见,需要维护
void add(int x, int y, int z) {
for (int i = x; i <= n; i += lowbit(i))
for (int j = y; j <= m; j += lowbit(j)) {
bit1[i][j] += z;
bit2[i][j] += (ll)x * z;
bit3[i][j] += (ll)y * z;
bit4[i][j] += (ll)x * y * z;
}
}
void chanhe(int x1,int y1,int x2,int y2,int x){
Update(x1, y1, x);
Update(x2 + 1, y2 + 1, x);
Update(x1, y2 + 1, -x);
Update(x2 + 1, y1, -x);
}