树状数组

树状数组

简单记录一下模板和用法,不做深入证明探究!

为什么不直接用前缀和

对于普通的前缀和来说,若出现了单点修改,则需要重新生成一个前缀和数组。若单点修改次数过多,显然会产生恐怖的代价。

能解决的问题:

  • 区间查询前缀和
  • 单点修改(某个值+一个数)

是一个在 logN复杂度就能完成以上操作的数据结构。严格来说,能解决的问题是线段树的子集。

树状数组能够解决的问题,线段树一定可以解决!但是树状数组代码简单好写,相比臃肿庞大的线段树,能用树状数组优雅的解决问题,想必也是让人神往的吧!

看个图例:

 

 

不难看出,树状数组是分层(高度)的。对于下标i在多少层,只需要将其转换成二进制,并看一下它后面有几个0就好了,若有k个0,则在2^k层

例如: (4)10 = (100)2 ,其二进制下有两个零,所以在2^2 = 4 层。

三个函数:

lowbit()

这个函数主要用来求一个数的二进制下尾数有k个0,并返回2^k。

int lowbit(int x){
    return x& -x;
}

add()

void add(int x,int v){
    //在 x 位置 加上 v
    for(int i=x;i<=n;i+=lowbit(i)) tr[i]+= v; 
}

如果不是添加,而是想替换呢?

只需要将参数V  改成 v-x即可!

query()

int query(int x){
    int res = 0;
    for(int i=x;i>0;i-=lowbit(i)) res+=tr[i];
    return res;
}

对于区间[x,y]的前缀和,我们只需要 query(y)-query(x-1) 即可!(这里用到了差分的思想)

 

初始化:

将tr数组开在全局变量,即初始化为0。对于每个输入的数,只需要调用add函数即可。

 

板子题:

这里有一个板子

 

 代码如下:

#include<iostream>
#include<cstdio>
using namespace std;
const int N = 100010;
int n,m;

int a[N],tr[N];
int lowbit(int x){
    return x& -x;
}
void add(int x,int v){
    //在 x 位置 加上 v
    for(int i=x;i<=n;i+=lowbit(i)) tr[i]+= v; 
}
int query(int x){
    int res = 0;
    for(int i=x;i>0;i-=lowbit(i)) res+=tr[i];
    return res;
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    for(int i=1;i<=n;i++) add(i,a[i]);
    
    while( m --){
        int k,x,y;
        scanf("%d%d%d",&k,&x,&y);
        if(k==0){
            printf("%d\n",query(y)-query(x-1));
        }
        else{
            add(x,y);
        }
    } 
    
} 

 

posted @ 2023-04-02 13:54  L1ngYi  阅读(14)  评论(0编辑  收藏  举报