树状数组 :一维 + 二维

部分内容来源:bestsort的博客

推荐博客:
CSDN:南宮逸辰 - 彻底弄懂二维树状数组
CSDN:Lv1_kangdi - 二维树状数组总结及模板

一维树状数组 sum[x] 记录的是 从1到 x,长度为lowbit(x) 的前缀和

二维树状数组 sum [x] [y] 记录的是 [1,1] 到 [x,y] , 高为lowbit(y) 长为lowbit(x) 的区域区间和

例题:树状数组 - 相关模板题


一维树状数组

图示:

img
  • 2进制表示
img

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=1xj=1yk=1ih=1jd[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=1xj=1yd[i][j](x+1i)(y+1j)

( 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=1xj=1yd[i][j](y+1)i=1xj=1yd[i][j]i(x+1)i=1xj=1yd[i][j]j+i=1xj=1yd[i][j]ij

即维护四个数组:
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);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值