树状数组

http://www.cppblog.com/Ylemzy/articles/98322.html

树状数组是对一个数组改变某个元素和求和比较实用的数据结构。两中操作都是O(logn)。 

  在解题过程中,我们有时需要维护一个数组的前缀和S[i]=A[1]+A[2]+...+A[i]。

          但是不难发现,如果我们修改了任意一个A[i],S[i]、S[i+1]...S[n]都会发生变化。

          可以说,每次修改A[i]后,调整前缀和S[]在最坏情况下会需要O(n)的时间。

          当n非常大时,程序会运行得非常缓慢。

          因此,这里我们引入“树状数组”,它的修改与求和都是O(logn)的,效率非常高。

【理论】

          为了对树状数组有个形 象的认识,我们先看下面这张图。


          如图所示,红色矩形表示的数组C[]就是树状数组。

          这里,C[i]表示A[i-2^k+1]到A[i]的和,而k则是i在二进制时末尾0的个数,

          或者说是i用2的幂方和表示时的最小指数。

         ( 当然,利用位运算,我们可以直接计算出2^k=i&(i^(i-1)) )

          同时,我们也不难发现,这个k就是该节点在树中的高度,因而这个树的高度不会超过logn。

          所以,当我们修改A[i]的值时,可以从C[i]往根节点一路上溯,调整这条路上的所有C[]即可,

          这个操作的复杂度在最坏情况下就是树的高度即O(logn)。  

          另外,对于求数列的前n项和,只需找到n以前的所有最大子树,把其根节点的C加起来即可。

          不难发现,这些子树的数目是n在二进制时1的个数,或者说是把n展开成2的幂方和时的项数,

          因此,求和操作的复杂度也是O(logn)。

          接着,我们考察这两种操作下标变化的规律:

          首先看修改操作:

          已知下标i,求其父节点的下标。
          我们可以考虑对树从逻辑上转化:


         如图,我们将子树向右对称翻折,虚拟出一些空白结点(图中白色),将原树转化成完全二叉树。

         有图可知,对于节点i,其父节点的下标与翻折出的空白节点下标相同。

         因而父节点下标 p=i+2^k  (2^k是i用2的幂方和展开式中的最小幂,即i为根节点子树的规模)

         即  p = i + i&(i^(i-1)) 。

         接着对于求和操作:

         因为每棵子树覆盖的范围都是2的幂,所以我们要求子树i的前一棵树,只需让i减去2的最小幂即可。

         即  p = i - i&(i^(i-1)) 。

        

         至此,我们已经比较详细的分析了树状数组的复杂度和原理。

         在最后,我们将给出一些树状数组的实现代码,希望读者能够仔细体会其中的细节。

【代码】

  求最小幂2^k:


int Lowbit(int t) 

    return t & ( t ^ ( t - 1 ) ); 

             
  求前n项和:


int Sum(int end) 

    int sum = 0; 
    while(end > 0) 
    { 
        sum += in[end]; 
        end -= Lowbit(end); 
    } 
    return sum; 


 对某个元素进行加法操作: 

void plus(int pos , int num) 

    while(pos <= n) 
    { 
          in[pos] += num; 
          pos += Lowbit(pos); 
    } 

http://www.cnblogs.com/183zyz/archive/2011/03/22/1991269.html

去年的暑假集训的时候,也看了下树状数组,那时候没学会就放下了,前两天下载一个课件又好好的看下,感觉也理解了一点 ^_^...

使用树状数组局感觉限性不小, 只有进行特殊的 题目才比较实用,  对一般的题目还是用线段树的好。。

以后坚持每天都写一篇博客吧,记录下每天的心得 ^_^

代码:

复制代码
   
   
/* 所谓的树状数组 */ # include < stdio.h > int a[ 20 ],m; int Lowbit( int n) { return n & (n ^ (n - 1 )); /// 2的k次幂==n&(n^(n-1)) ( ^ 是异或的意思,) k表示把n转化为2进制后 后面的0的个数 } int sum( int n) { int ans = 0 ; while (n > 0 ) { ans += a[n]; n -= Lowbit(n); } return ans; } void puls( int pos, int num) { while (pos <= 10 ) { a[pos] += num; pos += Lowbit(pos); } } int main() { int i,b[ 20 ],j,pos,num; for (i = 1 ;i <= 10 ;i ++ ) scanf( " %d " , & b[i]); for (i = 1 ;i <= 10 ;i ++ ) { a[i] = 0 ; for (j = i - Lowbit(i) + 1 ;j <= i;j ++ ) a[i] += b[j]; } scanf( " %d%d " , & pos, & num); puls(pos,num);// 第pos位上的数字增加num while (scanf( " %d " , & m) != EOF) { printf( " %d\n " ,sum(m));//求前m项的和 } return 0 ; }
复制代码

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值