树状数组 - 点更新、区间查询

本文深入讲解了树状数组这一高效数据结构,它在查询和修改上的复杂度均为log(n),适用于数组的单点修改和区间求和。文章对比了树状数组与线段树的区别,强调了树状数组在编程实现上的简洁性和效率优势。并通过具体实例,详细解释了树状数组的存储方式、查询和更新操作。

树状数组基础

树状数组是一个查询和修改复杂度都为log(n)的数据结构。主要用于数组的单点修改&&区间求和.

另外一个拥有类似功能的是线段树.

 

具体区别和联系如下:

1.两者在复杂度上同级, 但是树状数组的常数明显优于线段树, 其编程复杂度也远小于线段树.

2.树状数组的作用被线段树完全涵盖, 凡是可以使用树状数组解决的问题, 使用线段树一定可以解决, 但是线段树能够解决的问题树状数组未必能够解决.

3.树状数组的突出特点是其编程的极端简洁性, 使用lowbit技术可以在很短的几步操作中完成树状数组的核心操作,其代码效率远高于线段树。

(以上部分摘自:bestsort

 

上图是树状数组的结构,可以发现:

C[1]=A[1];

C[2]=A[1]+A[2];

C[3]=A[3];

C[4]=A[1]+A[2]+A[3]+A[4];

C[5]=A[5];

C[6]=A[5]+A[6];

C[7]=A[7];

C[8]=A[1]+A[2]+A[3]+A[4]+A[5]+A[6]+A[7]+A[8];

对照式子可以发现  C[i]=A[i-2^k+1]+A[i-2^k+2]+......A[i];

令lowbit=2^k

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

那么 C[i]=A[i-lowbit(i)+1]+A[i-lowbit(i)+2]+......A[i];

区间查询:

例如求区间[1,7]的值之和:

sum[7]=A[1]+A[2]+A[3]+A[4]+A[5]+A[6]+A[7] ;   前i项和

C[4]=A[1]+A[2]+A[3]+A[4];   C[6]=A[5]+A[6];   C[7]=A[7];

可以推出:   sum[7]=C[4]+C[6]+C[7];

序号写为二进制: sum[(111)]=C[(100)]+C[(110)]+C[(111)];

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

单点更新:

例如把点A[1]的值加m,要更改C[1],C[2],C[4],C[8]

(1) C[1]+=m

(1)+lowbit(1)=2  C[2]+=m

(10)+lowbit(2)=4  C[4]+=m

(100)+lowbit(4)=8  C[8]+=m

void add(int x){
    for(int i=x;i<=N;i+=lowbit(i)){
        c[i]++;
    }
}

要注意的是:

1.树状数组的存储空间为O(N),不像线段树要开N<<2的空间,因为它存的只有C[1],C[2],C[3],C[4]……C[N]。

2.树状数组从1开始存,因为0&(-0)=0无法进行更新。

一道模板题:

POJ2352 Stars

题意:有n颗星星,按“y轴从小到大,若y轴相等,按x轴从小到大”的规律给出,求出每个星星的的层数,层数的定义是“y坐标不超过该星星,且x坐标不超过该星星的星星个数”,输出在这个层数星星的个数

思路:由于星星给出的规则,当给出了i个星星时,y坐标大于yi的以及,y==yi&&x>xi的都没给出,而且也不参与星星的层数计算,给出的星星:y<=yi,所以只要找出x<=xi的星星个数就可以啦~

代码如下:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<string>
#include<cstring>
#include<queue>
#include<stack>
#include<cmath>
#include<set>
#include<map>
using namespace std;
#define ll long long
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
typedef pair<int,int>P;
const int INF=0x3f3f3f3f;
const int N=32010;
int c[N],num[N];
int lowbit(int i){
    return i&(-i);
}

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

void add(int x){//x点加一
    for(int i=x;i<=N;i+=lowbit(i)){
        c[i]++;
    }
}

int main(){
    int n,x,y;
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d%d",&x,&y);
        x++;
        int level=sum(x);
        num[level]++;
        add(x);
    }
    for(int i=0;i<n;i++){
        printf("%d\n",num[i]);
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值