数据结构:树状数组

什么是树状数组?

是用一种类似于二叉树的森林结构来模拟树形结构,顾名思义就是用数组模拟树形结构。

这是一个可以让算法的时间复杂度下降至与n转化成二进制数中的“1”的有关。

为什么不直接建树?

当然是因为它具有简便性,能用树状数组就不建树

树状数组的用途是?

它的基本用途是维护序列的前缀和。

简单来说就是可以用于求区间和,查询数,更新数等。

树状数组的结构?

这里介绍一种lowbit运算,lowbit(n)定义为非负整数n在二进制表示下“最低位的1及其后面所有的0”构成的数值。

通俗地讲,lowbit就是求数n的二进制中从低位开始,拢共有多少个连续0。

给定x,按二进制表示为:

x= 2^k +2^(k-1)+2^(k-2)+……+2^1+1;

那么x就能划分成以下区间:

(x-1 , x]

(x - 1 - 2^1, x-1 ]

(x - 1 - 2^1 - 2^2 , x - 1 - 2^1 ]

(x - 1 - 2^1 - 2^2 -2 ^3, x - 1 - 2^1 - 2^2 ]

………………

(0,x - 1 - 2^1 - 2^2 -2 ^3 -……- 2 ^ (k-1) ]

不难发现

这些区间,若区间的结尾数为r,则区间长度就是r的“二进制数”的最小2的次幂,即为lowbit(r).

且分成的区间数是log(n)的。

所以基于上述,若给定一个序列a,那建立一个c数组,

其中c[x]是保存a序列的 [ x - lowbit(x) +1, x ]中所有数的和。

举个栗子:

x=15=1111(二进制)

1~15的区间和sum=c[15]+c[14]+c[12]+c[8];

图示总结

重要的代码表示

lowbit函数不是系统自带的,需要手写


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

给定整数x,求[1,x]分成的log (n)个小区间


while(x){
    printf("[%d, %d]\n",x-lowbit(x)+1,x);
    x-=lowbit(x);
}

基本操作之-----在O(log n)的时间内查询区间和


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

基本操作之-----单点增加(即给数列中a[i]加上一个数,并且维护序列的前缀和)


void add(int x,int y){//y是要加上的数 
    while(x<=n){//n是数列长度 
        c[x]+=y;
        x+=lowbit(x); 
    }
}

下面是一道板子题

楼兰图腾

这与逆序对问题差不多

我们需要建立树状数组(初始化为零),进行单点增加操作就把a[i]上的数加1即可(意味数值a[i]又出现了一次)。在这就不过多解释


#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1e6+10;
LL a[N],c[N];
int zx[N],zd[N],yx[N],yd[N];
int n;
LL lowbit(int x){
    return x&(-x);
}
void gx(int x,int y){
    while(x<=n){
       c[x]+=y;
       x+=lowbit(x);
    }
}
LL qh(int x){
    LL ans=0;
    while(x){
        ans+=c[x];
        x-=lowbit(x);
    }
    return ans;
}
int main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        scanf("%lld",&a[i]);
        zx[i]=qh(a[i]);
        zd[i]=i-1-zx[i];
        gx(a[i],1);
    }
    memset(c,0,sizeof c);
    for(int i=n;i>=1;i--){
        yx[i]=qh(a[i]);
        yd[i]=n-i-yx[i];
        gx(a[i],1);
    }
    LL ans1=0,ans2=0;
    for(int i=1;i<=n;i++){
        ans1+=(LL)zd[i]*yd[i];
        ans2+=(LL)zx[i]*yx[i];
    }
    cout<<ans1<<" "<<ans2;
    return 0;
}

树状数组的区间增加?

若给定数组a,求将数列中的数l~r加上d

关于区间增加:

如果像上述一般查询,面子时间上肯定过不去

建一个数组b,对于每条指令,把b[l],加上d,b[r+1]减去d(差值)

不难发现,b数组的前缀和反映l指令对a数组的影响。那么就可以用树状数组来维护数组b的前缀和b[1~x],就得出a数组上增加的数值总和。

假设我们规定b[0] = 0;

某个区间[x,y]值改变了,区间内的差值是不变的,只有D[x]和D[y+1]的值发生改变

根据图

那么来个板子题吧

一个简单的整数问题

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值