【暖*墟】 #初级数据结构# 树状数组

73 篇文章 0 订阅
14 篇文章 0 订阅

        目录

lowbit

单点修改

查询前缀和

练习

优缺点

拓展:多维树状数组

拓展:树状数组与逆序对


树状数组

重点:维护前缀和;快速单点修改;快速区间求和;不能计算区间最值。

易错点:根节点一定要从1开始。因为lowbit(0)=0,会陷入死循环。

正整数x被 “ 二进制分解 ”,使区间[1,x]分成 logx 个子区间。(唯一分解性质)

小区间特点:若结尾为R,则【区间长度= R的二进制分解下“最小的2的次幂”】。

即 lowbit(R)。 // 取出二进制下最低位的1。

基本用途是维护序列的前缀和。即用c数组储存序列a每个小区间的和。

除树根外,每个内部节点 c [ x ] 的父节点是 c [ x + lowbit(x) ] 。

 

lowbit

它通过公式来得出k,其中k就是该值从末尾开始0的个数。

然后将其得出的结果加上x自身就可以得出当前节点的父亲节点的位置,

或者是x减去其结果就可以得出上一个父亲节点的位置。

比如当前是6,二进制就是0110,k为2,那么6+2=8,而C(8)则是C(6)的父亲节点的位置;

相反,6-2=4,则是C(6)的上一个父亲节点的位置。

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

注意:LOWBIT无法处理0的情况,因为它的结果也是0,那么最终就是一个死循环

 

单点修改

当我们要对最底层的值进行更新时,那么它相应的父亲节点存储的和也需要进行更新。

 

查询前缀和

而查询的时候,则需要向前进行统计。

int QUERY(x){
    result = 0;
    while(right > 0){
        result += fenwick[x]; x -= LOWBIT(x); 
        return result;
}

例如:

15=(1111)2,通过lowbit分解,可变成4数和:(1111)2=(1)2+(10)2+(100)2+(1000)2,

然后我们分析这个倒着跳的过程。减去15的最小的2的幂次2^0得到14。

减去14的最小的2的幂次2^1得到12。减去12的最小的2的幂次2^2得到8。

减去8的最小的2的幂次2^3得到0。

所以C(15) = C(14) + C(12) + C(8) + C(0),由图也可以得知,其结果是正确的。

除此之外,树状数组能够快速的求任意区间的和

设sum(k) = A[1] + A[2] + ... + A[k],则A[i] + A[i+1] + ... + A[j] = sum(j) - sum(i-1)。

 

练习

  • 输入n个位置,然后先按照y的顺序,如果相等则按照x的顺序,
  • 最终求该坐标左下的星星的数目。

【分析】根据题目要求,y已经满足要求,那么只需要考虑x即可,

那么我们就可以使用树状数组来计算前面的星星的数目。

#include<stdio.h> 
#include<string.h> 

int c[32000+10]; 
int a[15000+10]; 
int lowbit(int x) { return x&(-x); } 

void updata(int x,int d) {
    while(x<=32001) { 
        c[x]=c[x]+d; 
        x=x+lowbit(x); } 
} 

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

int main() { 
    int n; int i,x,y; 
    while(scanf("%d",&n)!=EOF) { 
        memset(c,0,sizeof(c)); 
        memset(a,0,sizeof(a)); 
        for(i=0; i<n; i++) { 
        //因为y是升序,所以横坐标小于x的所有点都符合,关键。    
            scanf("%d%d",&x,&y); 
            //下标可能从0开始,所以要x+1 
            a[getsum(x+1)]++; 
            //求出横坐标小于x的所有stars个数,并记录到a中 
            updata(x+1,1); //更新区间  
            for(i=0; i<n; i++) { 
                printf("%d\n",a[i]); 
            } 
    } 
    return 0; 
}

 

优缺点

树状数组的优点:

  1. 代码短小,实现简单;
  2. 容易扩展到高纬度的数据;

缺点:

  1. 只能用于求和,不能求最大/小值;
  2. 不能动态插入;
  3. 数据多时,空间压力大。

 

拓展:多维树状数组

https://blog.csdn.net/cggwz/article/details/78420102

 

拓展:树状数组与逆序对

将每个数该在的位置离散化出来,然后每次把每个数该在的位置上加上1。

比如一个数该在的位置为x,那么用add(x)把这个位置加上1,

然后再用区间查询read(x)查询1~x的和,也就是可以知道前面有多少个数是比他小的了(包括那个数自己)。

再用已经插入的数的个数减去read(x),就算出了前面有多少个数比他大了。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define LL long long
using namespace std;
 
int n,tree[100010];
void add(int k,int num){
    while(k<=n){
        tree[k]+=num; 
        k+=k&-k;
    }
}
 
int read(int k){
    int sum=0;
    while(k){
        sum+=tree[k];
        k-=k&-k;
    }
    return sum;
}

struct node{
    int val,pos;
}a[100010];

bool cmp(node a,node b){ return a.val < b.val; }

int main(void){
    int i,j,b[100010];
    while(scanf("%d",&n)==1){
        memset(tree,0,sizeof(tree));
        for(i=1;i<=n;i++){
            scanf("%d",&a[i].val);
            a[i].pos = i;
        }
        sort(a+1,a+1+n,cmp);
        int cnt = 1;
        for(i=1;i<=n;i++){
            if(i != 1 && a[i].val != a[i-1].val)
                cnt++;
            b[a[i].pos] = cnt;
        }
        LL sum = 0;
        for(i=1;i<=n;i++){
            add(b[i],1);
            sum += (i - read(b[i]));
        }
        printf("%lld\n",sum);
    }
    return 0;
}

 

                                               ——时间划过风的轨迹,那个少年,还在等你。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值