部分内容来源:bestsort的博客
推荐博客:
CSDN:南宮逸辰 - 彻底弄懂二维树状数组
CSDN:Lv1_kangdi - 二维树状数组总结及模板
一维树状数组 sum[x] 记录的是 从1到 x,长度为lowbit(x) 的前缀和
二维树状数组 sum [x] [y] 记录的是 [1,1] 到 [x,y] , 高为lowbit(y) 长为lowbit(x) 的区域区间和
例题:树状数组 - 相关模板题
一维树状数组
图示:
- 2进制表示
lowbit()函数
lowbit(x)表示 x的二进制表达式中最低位的1所对应的值
int lowbit(int x){
return x & (-x);
}
单点修改
- 从下往上更新,更新包含该点的区间
- 例如更新[5]时, 更新顺序为 sum[5] , sum[6], sum[8], sum[16] (n <= 16)
- 二进制表示为 sum[101], sum[110], sum[1000], sum[10000]
- 往上更新的点是 当前数+该数的二进制最低位1的值 ( x + lowbit(x) )
void updata(int id,int x,int n){
while(id <= n){
sum[id] += x;
id += lowbit(id);
}
}
区间查询
- 从下往上查询 区间前缀和
- 查询时 为当前查询数字区间 + 前面的区间和
- 例如查询[1, 13]区间时,查询区间为 sum[13], sum[12], sum[8] (sum[13] 是[13,13]的区间和, sum[12]是 [9,12] 的区间和 ,sum[8]是 [1,8]的区间和)
- 用二进制表示是: sum[1101] + sum[1100] + sum[1000]
- 往上查询的点为 当前数 - 该数二进制最低位1的值 (x - lowbit(x))
- 数组 sum[x] 记录的是 ( x-lowbit(x) , x ] 的区间前缀和 ( 注意是前开后闭)
int query(int id){
int res = 0 ;
while(id){
res += sum[id];
id -= lowbit(id);
}
return res;
}
区间修改+ 单点查询
差分: a[i] 为原数组,构造b[i] 数组,使得 b[i] = a[i] - a[i-1] ,即 a[i] = a[i-1] + b[i] --> a[i] 是b[i] 的前缀和
区间修改:
通过差分 将 a[i] 的区间修改转化为 b[i] 的单点修改:a[l,r] + x --> b[l] +x , b[r+1] -x
单点查询:
同样是利用差分 将其转化为b[i]的 区间查询:a[i] --> b[1,r]
int a[N],b[N]; // a[]原数组 ,b[]差分数组
int lowbit(int id){
return id & -id;
}
//单点查询
int query(int id){
int res = 0;
while(id) res += id, id -= lowbit(id);
return res;
}
// 差分数组的单点修改
void alter(int id,int x,int n){
while(id <= n) b[id] += x, id += lowbit(id);
}
//区间修改
int update(int l,int r,int x,int n) {
alter(l,x,n),alter(r+1,-x,n);
}
区间修改 + 区间查询
同样用差分,构建两个数组(下面说明原因):
sum1[i] = b[i]
sum2[i] = b[i] * i
区间修改原理基本同上:a[l,r]x --> sum1[l] + x, sum2[r+1] - x
区间查询:sum_a[l,r] --> (p+1) * sum1[i] - sum2[i] - ( (p+1) * sum1 - sum2[i] )
a[i] 是b[i] 的前缀和, a[i]的前缀和 是b[i] 的两重前缀和,如图:
a[i] = b[i] + b[i-1] + b[i-2] +… + b[1]
a[1] = b[1]
a[2] = b[1] + b[2]
a[3] = b[1] + b[2] + b[3]
a[4] = b[1] + b[2] + b[3] +b[4]
在a[p] 的前缀和中,b[1] 用了p次,b[2] 用了p-1次,类推可以得出(上式 3):
sum_a[p] = p * b[1] + (p-1) * b[2] + (p-3) * b[3] + …+2 * b[p-1]+ 1 * b[p]
sum_a[p] = (p-i+1) * b[i] = (p+1) * b[i] - i * b[i]
因此用两个式子来维护区间查询:
sum1[i] = b[i]
sum2[i] = b[i] * i
注意:维护sum2数组时, sum2[i] += id*x
int sum1[N], sum2[N];
//区间修改中的点修改
void alter(int id, int x, int n){
int i = id;
while(i <= n) sum1[i] += x, sum2[i] += id*x , i += lowbit(i);
}
// 区间修改
void update(int l,int r, int x, int n){
alter(l, x, n), alter(r+1, -x, n);
}
// 区间查询中的点查询
int query(int id){
int res = 0, i = id;
while(i){
res += (id+1) * sum1[i] - sum2[i], i -= lowbit(i);
}
return res;
}
// 区间查询
int range_query(int l,int r){
return query(l) - query(r-1);
}
二维树状数组
推荐博客:二维树状数组总结及模板:
区间查询 + 单点修改
int s[N][N], n ;
int lowbit(int x){
return x & -x;
}
// 单点修改
void updata(int x, int y, int d){
while(x <= n){
int yy = y;
while(yy <= n)
s[x][yy] += d, yy += lowbit(yy);
x += lowbit(x);
}
}
// 区间查询
int query(int x, int y){
int res = 0;
while(x){
int yy = y;
while(yy) res += s[x][yy], yy -= lowbit(yy);
x -= lowbit(x);
}
}
区间修改 + 单点查询
/**二维
* 区间修改 + 单点查询
* **/
int a[N][N], b[N][N];
void updata(int x,int y, int d){
while(x <= n){
int yy = y;
while(yy <= n)
b[x][yy] += d, yy += lowbit(yy);
x += lowbit(x);
}
}
// 修改
void range_updata(int x1,int y1,int x2,int y2, int d){
updata(x1, y1, d);
updata(x1+1, y2, -d);
updata(x2, y1+1, -d);
updata(x2+1, y2+1, d);
}
// 查询
int query(int x, int y){
int res = 0;
while(x){
int yy = y;
while(yy) res += b[x][yy], yy -= lowbit(yy);
x -= lowbit(x);
}
}
区间修改 + 区间查询
关于点(x,y)的二维前缀和
∑ i = 1 x ∑ j = 1 y ∑ k = 1 i ∑ h = 1 j d [ h ] [ k ] \sum_{i=1}^{x}\sum_{j=1}^{y}\sum_{k=1}^{i}\sum_{h=1}^{j}d[h][k] i=1∑xj=1∑yk=1∑ih=1∑jd[h][k]
∑ i = 1 x ∑ j = 1 y d [ i ] [ j ] ∗ ( x + 1 − i ) ∗ ( y + 1 − j ) \sum_{i=1}^{x}\sum_{j=1}^{y}d[i][j] * (x+1-i) * (y+1-j) i=1∑xj=1∑yd[i][j]∗(x+1−i)∗(y+1−j)
( x + 1 ) ∗ ( y + 1 ) ∗ ∑ i = 1 x ∑ j = 1 y d [ i ] [ j ] − ( y + 1 ) ∗ ∑ i = 1 x ∑ j = 1 y d [ i ] [ j ] ∗ i − ( x + 1 ) ∗ ∑ i = 1 x ∑ j = 1 y d [ i ] [ j ] ∗ j + ∑ i = 1 x ∑ j = 1 y d [ i ] [ j ] ∗ i ∗ j (x+1)*(y+1)*\sum_{i=1}^{x}\sum_{j=1}^{y}d[i][j]-(y+1)*\sum_{i=1}^{x}\sum_{j=1}^{y}d[i][j]*i-(x+1)*\sum_{i=1}^{x}\sum_{j=1}^{y}d[i][j]*j+\sum_{i=1}^{x}\sum_{j=1}^{y}d[i][j]*i*j (x+1)∗(y+1)∗i=1∑xj=1∑yd[i][j]−(y+1)∗i=1∑xj=1∑yd[i][j]∗i−(x+1)∗i=1∑xj=1∑yd[i][j]∗j+i=1∑xj=1∑yd[i][j]∗i∗j
即维护四个数组:
t1[i] [i] = d[i] [j]
t2[i] [j] = d[i] [j] * i
t3[i] [j] = d[i] [j] * j
t4[i] [j] = d[i] [j] * i * j
int t1[N][N], t2[N][N], t3[N][N], t4[N][N];
void updata(int x, int y, int d){
while(x <= n){
int yy = y;
while(yy <= n){
t1[x][yy] += d;
t2[x][yy] += d*x;
t3[x][yy] += d*y;
t4[x][yy] += d*x*y;
yy += lowbit(yy);
}
x += lowbit(x);
}
}
//区间修改
void range_updata(int x1,int y1,int x2,int y2,int d){
updata(x1, y1, d);
updata(x1 + 1, y2, -d);
updata(x2, y1 + 1, -d);
updata(x2 + 1, y2 + 1, d);
}
int query(int x, int y){
int res = 0;
while(x){
int yy = y;
while(yy){
res += t1[x][yy]*(x+1)*(y+1) - t2[x][yy]*(y+1) - t3[x][yy]*(x+1) + t4[x][yy];
yy -= lowbit(yy);
}
x-=lowbit(x);
}
}
//区间查询
int range_query(int x1,int y1,int x2,int y2){
return query(x2,y2) - query(x1-1,y2) - query(x2,y1-1) + query(x1-1,y1-1);
}