• 【数据结构】树状数组


    引入

    对于一个数列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
    ",ans[i]);
    }
    View Code
  • 相关阅读:
    bootstrap表头固定
    JS:二维数组排序和获取子级元素
    JavaScript 变量声明提升
    一道经典面试题-----setTimeout(function(){},0)
    排序
    基础知识:Promise(整理)
    几个兼容相关的重要函数
    JSON
    关于由ajax返回的数据在for循环中只能取到最后一个数的问题
    如果要遍历除了for循环,你还知道什么?——JavaScript的各种遍历方式
  • 原文地址:https://www.cnblogs.com/xyfs99/p/11815046.html
Copyright © 2020-2023  润新知