引入

对于一个数列S=a1+a2+...+ak+...+an,我们有以下操作

1.区间求和:如要算[3,n-1]区间的和,则可以用前缀和Sn-1 - S3

2.对于ak,我们要加上d,则可以直接对ak进行操作

对于单步操作,如区间求和,它的时间复杂度为O(n),更新某个值为O(1)。但这仅仅是对于单步操作而言,若允许两个操作都进行,则时间复杂度就不止于此。于是,我们引进了树状数组,它的时间复杂度只有O(log2n)。

具体操作

首先,我们定义数组sum为前缀和,再定义数组tree(其作用后面会讲),ai为具体的值。首先看一张图:

 

 图中,我们可以清晰地知道,tree[1]=a1,tree[2]=a1+a2,tree[3]=a3,tree[4]=a4+a3+a2+a1,tree[5]=a5,tree[6]=a5+a6,  .........。那为什么tree是这样赋值的呢?我们可以观察到

8=1000,此时tree[8]=a1+...+a8,7=111,tree[7]=a7,6=110,tree[6]=a5+a6, 可知,tree数组的赋值个数由下表决定。它由多少个数值相加取决于下标的二进制末尾有几个0,有k个0,就有2k个值相加。8末尾有三个0,就有2^3个值相加。那我们如何找到有几个0呢?实际上,我们可以转化为找到下标的最后一个1所在位置。这里有一个神奇的操作lowbit(x)=x&(-x),就能实现。

其原理是利用负数的补码表示,负数的补码是原码取反加一。例如x=6=00000110,-x=11111010,lowbit(x)=2

那我们如何求和呢?

sum[4]=tree[4]

sum[7]=tree[7]+tree[6]+tree[4]

sum[9]=tree[9]

根据图表我们很容易得到上述关系,但是如果不画图呢?可以观察到:如7

7=111

6=110

4=100

可以联系上文可知,从7开始,先加上tree[7],然后7-lowbit(7)=6,加上tree[6],接着6-lowbit(6)=4,加上tree[4],最后4-lowbit(4)=0,结束。

int sum(int x){
    int s=0;
    while(x>0){
        s+=tree[x];
        x-=lowbit(x);
    }
    return s;
}

tree数组的更新

更改ak,和它相关的tree[]都会改变。例如改变a3,那么tree[3]、、tree[4]、tree[8]等都会改变。同样这个计算也利用了lowbit(x)。

首先改变tree[3],然后3+lowbit(3)=4,更改tree[4],4+lowbit( 4 )=8,更改tree[8],知道最后的tree[n]。

void add(int x,int d){
    while(x<=n){
        tree[x]+=d;
        x+=lowbit(x);
    }
}

例题:poj2182

题意:n头牛,身高为1-n,告诉你从第二只牛开始,告诉你前面有prei只牛比它矮,要你输出这个序列牛的身高。

分析:从最后一只牛开始,prei+1就是他的身高,每确定一只,就减去1,往前推。

#include <iostream>
#include<cstdio>
using namespace std;
const int N=1e5;
int tree[N],pre[N],ans[N];
int n;
#define lowbit(x) ((x)&(-x))
void add(int x,int d){
    while(x<=n){
        tree[x]+=d;
        x+=lowbit(x);
    }
}
int sum(int x){
    int s=0;
    while(x>0){
        s+=tree[x];
        x-=lowbit(x);
    }
    return s;
}
int findpos(int x){
    int l=1,r=n;
    while(l<r){
        int mid=(l+r)/2;
        if(sum(mid)<x)
            l=mid+1;
        else
            r=mid;

    }
}
int main(){
    cin>>n;
    pre[1]=0;
    for(int i=2;i<=n;i++)
        scanf("%d",&pre[i]);
    for(int i=1;i<=n;i++)
        tree[i]=lowbit(i);
    for(int i=n;i>=1;i--){
        int x=findpos(pre[i]+1);
        //cout<<x<<endl;
        add(x,-1);
        ans[i]=x;
    }
    for(int i=1;i<=n;i++)
        printf("%d\n",ans[i]);
}
View Code

 

 posted on 2019-11-07 19:42  qmzhna  阅读(163)  评论(0编辑  收藏  举报