zoukankan      html  css  js  c++  java
  • BIT 树状数组 详解 及 例题

    (一)树状数组的概念  

    如果给定一个数组,要你求里面所有数的和,一般都会想到累加。但是当那个数组很大的时候,累加就显得太耗时了,时间复杂度为O(n),并且采用累加的方法还有一个局限,那就是,当修改掉数组中的元素后,仍然要你求数组中某段元素的和,就显得麻烦了。所以我们就要用到树状数组,他的时间复杂度为O(lgn),相比之下就快得多。下面就讲一下什么是树状数组:

        一般讲到树状数组都会少不了下面这个图:

    下面来分析一下上面那个图看能得出什么规律:

             据图可知:c1=a1,c2=a1+a2,c3=a3,c4=a1+a2+a3+a4,c5=a5,c6=a5+a6,c7=a7,c8=a1+a2+a3+a4+a5+a6+a7+a8,c9=a9,c10=a9+a10,c11=a11........c16=a1+a2+a3+a4+a5+.......+a16。

             分析上面的几组式子可知,当 i 为奇数时,ci=ai ;当 i 为偶数时,就要看 i 的因子中最多有二的多少次幂,例如,6 的因子中有 2 的一次幂,等于 2 ,所以 c6=a5+a6(由六向前数两个数的和),4 的因子中有 2 的两次幂,等于 4 ,所以 c4=a1+a2+a3+a4(由四向前数四个数的和)。

            (一)有公式:cn=a(n-a^k+1)+.........+an(其中 k 为 n 的二进制表示中从右往左数的 0 的个数)。

             那么,如何求 a^k 呢?求法如下:

    1 int lowbit(int x) //取x的最低位1,比如4,则返回4,如5,则返回1
    2 {
    3     return x&(-x);
    4 }
    View Code

    lowbit()的返回值就是 2^k 次方的值。

             求出来 2^k 之后,数组 c 的值就都出来了,接下来我们要求数组中所有元素的和。

             (二)求数组的和的算法如下:

             (1)首先,令sum=0,转向第二步;

             (2)接下来判断,如果 n>0 的话,就令sum=sum+cn转向第三步,否则的话,终止算法,返回 sum 的值;

             (3)n=n - lowbit(n)(将n的二进制表示的最后一个零删掉),回第二步。

              代码实现:

     1 int Sum(int i)   //求前i项的和
     2 {
     3     int s = 0;
     4     //将前i项分段
     5     while(i > 0)
     6     {
     7         s += sum[i];
     8         i -= lowbit(i);  //去掉i的二进制最后一个
     9     }
    10     return s;
    11 }
    View Code

     (三)当数组中的元素有变更时,树状数组就发挥它的优势了,算法如下(修改为给某个节点 i 加上 x ):

             (1)当 i<=n 时,执行下一步;否则的话,算法结束;

             (2)ci=ci+x ,i=i+lowbit(i)(在 i 的二进制表示的最后加零),返回第一步。

              代码实现:

    1 void update(int i, int val)  //将第i个元素增加val
    2 {
    3     //i的祖先都要增加val
    4     while(i <= n)
    5     {
    6         sum[i] += val;
    7         i += lowbit(i);   //将i的二进制未位补为得到其祖先
    8     }
    9 }
    View Code

    (二)树状数组的应用

    以下数组下标均默认从1开始

    应用一

    假如给你一个数组a[ ] = {2,5,3,4,1},b[i]b[i] 表示在a[1],a[2]...a[i-1](即位置i左边)小于等于a[i]的数的个数。对此例b[] = {0,1,1,2,0}。 那么该如何去求得b[i]呢?

    解法:假如要得到b[4]的值,对于a[4] = 4. 我们 只要得到在a[1],a[2],a[3] 中出现小于等于4的个数,即1,2,3,4的个数,此例即为2. a[1] = 2 < a[4], a[3] = 3 < a[4]. 所以b[4] = 2;其他的以此类推b[i]的值,需要得到在a[1],a[2]....a[i-1]中出现小于等于a[i]的个数,即1,2...a[i]的个数相当于求前a[i]项的和,可用到树状数组

    具体操作

    for(int i=1; i<=n; i++)

    {

    b[i] = getSum(a[i]); //求前a[i]项的和

    update(a[i],1);      //a[i]个元素+1

    }

    应用二

    假如给你一个数组a[ ] = {2,5,3,4,1},b[i]b[i] 表示在a[1],a[2]...a[i-1](即位置i左边)大于等于a[i]的数的个数。对此例b[] = {0,0,1,1,4}。 那么该如何去求得b[i]呢?

    解法1只需要先将数组a倒过来编号,即将a转换为,a[] ={4,1,3,2,5}.此时具体的操作如应用一

    以下数组下标均默认从1开始

    应用一

    假如给你一个数组a[ ] = {2,5,3,4,1},b[i]b[i] 表示在a[1],a[2]...a[i-1](即位置i左边)小于等于a[i]的数的个数。对此例b[] = {0,1,1,2,0}。 那么该如何去求得b[i]呢?

    解法:假如要得到b[4]的值,对于a[4] = 4. 我们 只要得到在a[1],a[2],a[3] 中出现小于等于4的个数,即1,2,3,4的个数,此例即为2. a[1] = 2 < a[4], a[3] = 3 < a[4]. 所以b[4] = 2;其他的以此类推b[i]的值,需要得到在a[1],a[2]....a[i-1]中出现小于等于a[i]的个数,即1,2...a[i]的个数相当于求前a[i]项的和,可用到树状数组

    具体操作

    for(int i=1; i<=n; i++)

    {

    b[i] = getSum(a[i]); //求前a[i]项的和

    update(a[i],1);      //a[i]个元素+1

    }

    应用二

    假如给你一个数组a[ ] = {2,5,3,4,1},b[i]b[i] 表示在a[1],a[2]...a[i-1](即位置i左边)大于等于a[i]的数的个数。对此例b[] = {0,0,1,1,4}。 那么该如何去求得b[i]呢?

    解法1只需要先将数组a倒过来编号,即将a转换为,a[] ={4,1,3,2,5}.此时具体的操作如应用一

    解法2:改变更新路径和求和路径

     1 void update(int x, int val)
     2 {
     3     for(int i=x; i>0; i-=lowbit(i))
     4     {
     5         sum[i] += val;
     6     }
     7 }
     8 
     9 int getSum(int x)
    10 {
    11     int s = 0;
    12     for(int i=x; i<MAXN; i+=lowbit(i))
    13     {
    14         s += sum[i];
    15     }
    16     return s;
    17 }
    View Code

    应用三  逆序数

    假如给你一个数组a[ ] = {2,5,3,4,1},b[i]b[i] 表示在a[i],a[i+1]...a[n](即位置i右边)小于等于a[i]的数的个数。对此例b[] = {1,3,1,1,0}。 那么该如何去求得b[i]呢?

    操作:应用一位置i的左边,应用三是位置i的右边。 然后只需要在应用一的基础上从后往前操作即可

    1 for(int i=n; i>=1; i--)
    2 
    3 {
    4 
    5 b[i] = getSum(a[i]); //求前a[i]项的和
    6 
    7 update(a[i],1);      //第a[i]个元素+1
    8 
    9 }
    View Code

    应用四

    假如给你一个数组a[ ] = {2,5,3,4,1},b[i]b[i] 表示在a[i],a[i+1]...a[n](即位置i右边)大于等于a[i]的数的个数。对此例b[] = {3,0,1,0,0}。 那么该如何去求得b[i]呢?

    操作:只需将数组a倒过来编号,即将a转化为 a[]={4,1,3,2,5} 然后利用应用三

    二维树状数组

     1 int lowbit(int x)
     2 {
     3     return x&(-x);
     4 }
     5 
     6 void update(int x, int y, int val) //将 a[x][y] 的值增加val
     7 {
     8     for(int i=x; i<N; i+=lowbit(i))
     9     {
    10         for(int j=y; j<N; j+=lowbit(j))
    11         {
    12             sum[i][j] += val;
    13         }
    14     }
    15 }
    16 
    17 
    18 int getSum(int x, int y) //求以1,1为左上角端点,学校,x,y为右下角端点的矩阵和.
    19 {
    20     int s = 0;
    21     for(int i=x; i>0; i-=lowbit(i))
    22     {
    23         for(int j=y; j>0; j-=lowbit(j))
    24         {
    25             s += sum[i][j];
    26         }
    27     }
    28     return s;
    29 }
    View Code

    (三)例题

    基础应用

    HDU 1166 敌兵布阵(树状数组)

    http://www.cnblogs.com/ws5167/p/3904004.html

    HDU 2689 Sort it (树状数组)

    http://www.cnblogs.com/ws5167/p/3915614.html

    HDU 2492 Ping pong (树状数组)

    HDU Cow Sorting (树状数组)

    二维树状数组

    HDU1559 最大子矩阵 (二维树状数组)

    HDU 1892 See you~ (二维树状数组)

    三维树状数组

    HDU 3584 Cube (三维 树状数组)

    树状数组求逆序数

    HDU 1394 Minimum Inversion Number ( 树状数组求逆序数 )

    DP+树状数组+离散化

    HDU 2227 Find the nondecreasing subsequences (DP+树状数组+离散化)

     
     
  • 相关阅读:
    Source Maps简介
    JavaScript数据结构——图的实现
    JavaScript数据结构——树的实现
    JavaScript数据结构——字典和散列表的实现
    JavaScript数据结构——集合的实现与应用
    [转]通过设置nginx的client_max_body_size解决nginx+php上传大文件的问题
    安卓刷量技术揭秘
    【转】让Bootstrap 3兼容IE8浏览器
    [LINK]Python服务器开发一:python基础
    [LINK]用Python计算昨天、今天和明天的日期时间
  • 原文地址:https://www.cnblogs.com/ws5167/p/3903970.html
Copyright © 2011-2022 走看看