树状数组

树状数组

利用二进制的一些性质使得区间修改、区间查询效率达到log的数据结构

  • lowbit

    lowbit(x)求出二进制x中末尾的1

    公式:lowbit(x)=x&-x;

    举个例子:

    
    计算时用补码,正数的补码是其原码,负数的补码是反码+1
            14(00001110)
          &-14(11110010)
          _______________
               00000010
    
  • 特殊性质

树状数组的节点c[i]一般用来表示a[i-lowbit(i)+1]到a[i]的和,c[i]的父亲节点为c[i+lowbit(i)]

树状数组

显然一个节点并不一定是代表自己前面所有元素的和。只有满足 2^n这样的数才代表自己前面所有元素的和。

  • 操作

1.单点修改

假设修改a[x],由于树状数组要修改若干个连续数的和,修改了a[x]之后,c[x]--c[n]可能也会发生变化,因此要向上更新。

假设a[x]增加了x,则sum[x]-- sumn均增加了x,所以只要把增量向上更新即可。

void update(int id,int x){//点的编号 增量
    while(id<=n){
        c[id]+=x;
        id+=lowbit(id);//到达父亲节点
    }
}

2.单点查询

假设要求出a[1]---a[x]的和,根据lowbit的性质,a[x]的子节点为a[x-lowbit(x)],一直向下查询,直到x减到零为止

int query(int x){
    int res=0;
    while(x){
        res+=c[x];
        x-=lowbit(x);
    }
    return res;
}

3.区间修改单点查询

引入一插值数组b,定义b[i]=a[i]-a[i-1],用b[i]建立树状数组。对于每一个a[i],都有a[i]=b[1]+b[2]+...+b[i],前缀和查询就相当于单点查询

设要求把x--y加上data,则x--y之间的差值不变,1--x-1之间的差值不变,y+2--n之间的差值不变,b[x]=a[x]+data-a[x-1],b[y+1]=a[y+1]-(a[y]+data),只需要把这两个值更新即可

易错点
设要求把x--y加上data,不能写成update(x,data);update(y+1,data)(这里树状数组维护的原数组),这样只是修改了两个端点,中间的值没有修改

4.区间修改区间查询

sum[i]=a1+a2+a3+a4+..+a[i]

=b[1]+(b[1]+b[2])+(b[1]+b[2]+b[3])+...+(b[1]+b[2]+b[3]+...+b[i])

=i * b[1]+(i-1)* b[2]+(i-2)* b[3]+...+2 * b[i-1]+b[i]

=i * (b[1]+b[2]+b[3]+...+b[i])-(0 * b[1]+1 * b[2]+2 *b [3]+...+ (i-1) * b[i])

用一个数组c1维护b[i],另一个c2维护(x-1)*b[i]

答案为i* query(i,c1)-query(i,c2)

void update(int x,long long data){
     int i=x;
     while(i<=n){
         c[i]+=data;
         c2[i]+=(x-1)*data;
         i+=lowbit(i);
     }
}
long long query(int x){
     long long ans=0;
     int i=x;
     while(i>0){
         ans+=c[i]*x-c2[i];
         i-=lowbit(i);
     }
     return ans;
}

二维树状数组

c[i][j]维护以(i,j)为左下角的宽为lowbit(i),长为lowbit(j)的矩阵内元素之和(具体视情况)

1.单点修改,区间查询

void update(int x,int y,int data){
  for(int i=x;i<=n;i+=lowbit(i)){
      for(int j=y;j<=m;j+=lowbit(j)){
          c[i][j]+=data;
      }
  }
}
int query(int x,int y){
  int ans=0;
  for(int i=x;i;i-=lowbit(i)){
      for(int j=y;j;j-=lowbit(j)){
          ans+=c[i][j];
      }
  }
  return ans;
}

假设要求左下s角坐标为(a,b)到右上角(c,d)矩阵的和,令sum[i][j]表示(1,1)到右上角为(i,j)矩阵元素之和,则所求矩阵元素和为sum[c][d]-sum[a-1][d]-sum[c][d-1]+sum[a-1][b-1]

即ans=query(c,d)-query(c,b-1)-query(a-1,d)+query(a-1,b-1);

2.区间修改单点查询

仍要引入一差分数组。

由于数组a[i][j]的前缀和有这样的公式:

  sum[i][j]=(sum[i−1][j]+sum[i][j−1]−sum[i−1][j−1])+a[i][j]

套用求前缀和的形式,假设差分数组为d[i][j],可以有:

  a[i][j]=(a[i−1][j]+a[i][j−1]−a[i−1][j−1])+d[i][j]

就能得到:

  d[i][j]=a[i][j]−(a[i−1][j]+a[i][j−1]−a[i−1][j−1])

假设要给(x1,y1)到(x2,y2)的矩阵全部加上x,差分数组变动的只有:
d[x1][y1]+=x,d[x1][y2+1]-=x,d[x2+1][y1]-=x;d[x2+1][y2+1]+=x;

posted @   Chano_sb  阅读(28)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示