zoukankan      html  css  js  c++  java
  • 树状数组(BIT)

    树状数组的原英文表达:Binary Indexed TreeBIT,直译的意思便是:二进制标记树

     

    如果数组A是基础数组,数组C是区间数组。那么,在具体介绍数组C的特点前,先给出如下的树状关系图:

     

    仔细观察上图,容易发现:

    数组C[]分别代表的区间为:

    C1=A1 [1,1]

    C2=C1+A2=A1+A2 [1,2]

    C3=A3 [3,3]

    C4=C2+C3+A4=A1+A2+A3+A4 [1,4]

    C5=A5 [5,5]

    C6=C5+A6=A5+A6 [5,6]

    C7=A7 [7,7]

    C8=C4+C6+C7+A8=A1+A2+A3+A4+A5+A6+A7+A8 [1,8]

     

    C9=A9 [9,9]

     

    每个 c[i] 管理的区间是   [i - bitlow(i) + 1, i] 

     

    也就是说,每个数组Ci,至少包含Ai,同时包含所有满足j+lowest_bit(j)=iCj数组。例如C8不仅包含A8,同时还包含了C4,C6,C7。而

     

    410=10021002+1002=10002=810

     

    610=11021102+102=10002=810

     

    710=11121112+12=10002=810

     

    lowest_bit(i)表示计算数字i的二进制表示中,从右往左数,第一个1所代表的数字。

     

    利用位运算,

     

    我们容易得到lowest_bit()的快速计算方法:

     

    int lowbit(int x)

     

    {

     

       return x&-x;

     

    }

     

    于是,在Ai更新时,只需纵向分别更新即可:C[i]C[i=i+lowest_bit(i)]……直到i>n

     

    例如在更新A1时候,我们分别更新C1,C2,C4C8。(这里假设n=9

    对于更新过程我们可以这样理解:更新所有包含Ai的数组Cj计算下标j的过程类似于在树形结构中寻找父节点的过程


    实现代码:

    voidadd(int x,int val)

    {

      while(x<=n)

      {

        c[x]+=val;

        x+=lowbit(x);

       }

    }


    对于每个数组Ci,至少包含Ai,同时包含所有满足j+lowest_bit(j)=iCj数组。因此,可以得到如下结论:数组Ci代表的区间一定是:[i-lowest_bit(i)+1,i]。(这里略去了该结论的证明过程)

    于是,对于A1+A2+……Ai的和,我们只需找到一组能完美覆盖区间[1,i]的数组集合{C[]}即可:C[i]+C[i=i-lowest_bit(i)]+……直到i=0


    例如在查询A1+A2+……A7的值时,我们累加C7+C6+C4

     

     


    实现代码:

    intsum(int x)

    {

      int rt=0;

      while(x)

      {

        rt+=c[x];

        x-=lowbit(x);

      }

       return rt;

    }


    可以看出,树状数组的代码实现非常简洁,极易编码。同时,我们容易计算出树状数组的更新操作的时间复杂度为log(n),查询操作的时间复杂度同样为log(n),因此总时间复杂度为log(n)

    [区间更新单一查询]


    若需要区间更新,单一查询,那么只要改变数组C的含义即可:数组C表示区间共同增量,例如,

    C1=A1 [1,1]

    C2=C1+A2=A1+A2 [1,2]

    C3=A3 [3,3]

    C4=C2+C3+A4=A1+A2+A3+A4 [1,4]

    分别表示区间[1,1][1,2][3,3][1,4]的共同增量,于是在更新区间[1,i]时,我们只需找到一组能完美覆盖区间[1,i]的数组集合{C[]}即可,这刚好对应着之前树状数组的sum操作。于是,我们将sum操作更改为add操作,即

    voidadd(int x, intval)

    {

    while(x)

    {

    c[x]+=val;

    x-=lowbit(x);

    }

    }

    对于区间[s,t],我们只需执行add(t,val)add(s-1,-val)即可。


    对于单一查询:query(i),我们只需累加所有包含Ai的数组Cj即可,这对应着之前树状数组的add操作。于是,我们将add操作更改为sum操作,即

    intsum(int x)

    {

    int rt=0;

    while(x<=n)

    {

    rt+=c[x];

    x+=lowbit(x);

    }

    return rt;

    }



    [区间更新区间查询]


    更一般的,有时候题目同时要求我们区间更新与区间查询,例如:


    Description

    You have N integers, A1, A2, ... , AN. You need to deal with two kinds of operations. One type of operation is to add some given number to each number in a given interval. The other is to ask for the sum of numbers in a given interval.

    Input

    The first line contains two numbers N and Q. 1 ≤ N,Q ≤ 100000.
    The second line contains
     N numbers, the initial values of A1, A2, ... , AN. -1000000000 ≤ Ai ≤ 1000000000.
    Each of the next
     Q lines represents an operation.
    "C
     a b c" means adding c to each of Aa, Aa+1, ... , Ab. -10000 ≤ c ≤ 10000.
    "Q
     a b" means querying the sum of Aa, Aa+1, ... , Ab.

    Output

    You need to answer all Q commands in order. One answer in a line.

    Sample Input

    10 5

    1 2 3 4 5 6 7 8 9 10

    Q 4 4

    Q 1 10

    Q 2 4

    C 3 6 3

    Q 2 4

    Sample Output

    4

    55

    9

    15

    Hint

    The sums may exceed the range of 32-bit integers.

    Source

    POJ Monthly--2007.11.25, Yang Yi



    这个题目求的是某一区间的数组和,而且要支持批量更新某一区间内元素的值,怎么办呢?实际上,还是可以把问题转化为求数组的前缀和

     

    首先,看更新操作update(s, t, d)把区间A[s]……A[t]都增加d,我们引入一个数组delta[i],表示

    A[i]……A[n]的共同增量,n是数组的大小。那么update操作可以转化为:

    1)令delta[s] = delta[s] + d,表示将A[s]……A[n]同时增加d,但这样A[t+1]……A[n]就多加了d,所以

    2)再令delta[t+1] = delta[t+1] - d,表示将A[t+1]……A[n]同时减d


    看查询操作query(s, t),求A[s]……A[t]的区间和,转化为求前缀和,设sum[i] = A[1]+ ……+A[i]

    A[s]+ ……+A[t] = sum[t] - sum[s-1]


    那么前缀和sum[x]又如何求呢?它由两部分组成,一是数组的原始和,二是该区间内的累计增量和把数组A的原始值保存在数组org中,并且delta[i]sum[x]的贡献值为delta[i]*(x+1-i)

    那么

     sum[x] = org[1]+……+org[x] + delta[1]*x + delta[2]*(x-1) +……+delta[x]*1

          = org[1]+……+org[x] + segma(delta[i]*(x+1-i))

         = segma(org[i]) + (x+1)*segma(delta[i])

    - segma(delta[i]*i)1 <= i <= x

    // segma,即求和


    这其实就是三个数组org[i], delta[i]delta[i]*i的前缀和,org[i]的前缀和保持不变,事先就可以求出来,delta[i]delta[i]*i的前缀和是不断变化的,并且每次都是单一更新所以可以用两个树状数组来维护。


    对于delta[i]*i,其实就delta[i]简单映射关系,于是update操作可以转化为:

    1)令delta[s]*s = (delta[s] + d)*s = delta[s]*s+s*d

    2)再令delta[t+1]*(t+1) = delta[t+1]*(t+1)(t+1)*d


    于是,我们有了如下的代码实现:

    scanf("%s",opt);

    if(opt[0]=='Q')

    {

    LL res;

    scanf("%d%d",&a,&b);

    res=org[b]+(b+1)*sum(b,0)-sum(b,1);

    res-=(org[a-1]+a*sum(a-1,0)-sum(a-1,1));

    printf("%I64d ",res);

    }

    else

    {

    scanf("%d%d%d",&a,&b,&c);

    add(a,c,0),add(b+1,-c,0);

    add(a,c*a,1),add(b+1,-c*(b+1),1);

    }


    细数BIT各种用法,不得不感叹这种简单数据结构的强大与灵活!


    趁热打铁,几道简单BIT

    hdu 1166 敌兵布阵

    pku 2299 Ultra_QuickSort

    hdu 1394 Minimum Inversion Number

    pku 1195 Mobile phones

    hdu 1892 See you~

    pku 2352 Stars

    hdu 2492 Ping pong

    pku 1990 MooFest

    pku 3067 Japan


    BIT的巧妙运用:

    pku 2155 Matrix

    hdu 3584 Cube

    pku 3321 Apple Tree

    pku 3378 Crazy Thairs


    BIT解决区间第k大元素:

    pku 2761 Feed the dogs

    pku 2886 Who Gets the Most Candies?


    BIT的区间维护与区间查询:

    pku 3468 A Simple Problem with Integers

    转载自:YB大神

     

     

     

  • 相关阅读:
    129 01 Android 零基础入门 02 Java面向对象 06 Java单例模式 03 饿汉模式 VS 懒汉模式 02 懒汉式的代码实现
    128 01 Android 零基础入门 02 Java面向对象 06 Java单例模式 03 饿汉模式 VS 懒汉模式 01 饿汉式的代码实现
    127 01 Android 零基础入门 02 Java面向对象 06 Java单例模式 02 单例模式概述 01 单例模式的定义和作用
    126 01 Android 零基础入门 02 Java面向对象 06 Java单例模式 01 设计模式概述 01 设计模式简介
    125 01 Android 零基础入门 02 Java面向对象 05 Java继承(下)05 Java继承(下)总结 01 Java继承(下)知识点总结
    leetcode-----121. 买卖股票的最佳时机
    leetcode-----104. 二叉树的最大深度
    Json串的字段如果和类中字段不一致,如何映射、转换?
    Mybatis-Plus的Service方法使用 之 泛型方法default <V> List<V> listObjs(Function<? super Object, V> mapper)
    模糊查询
  • 原文地址:https://www.cnblogs.com/tenlee/p/4775231.html
Copyright © 2011-2022 走看看