树状数组

来源:互联网 发布:风云无双天劫进阶数据 编辑:程序博客网 时间:2024/06/06 12:58

好久都对这个东西有点陌生,看了一下午,终于能说出点缘由来了。。

从网上找的点资料。。。

一、树状数组是干什么的?

       平常我们会遇到一些对数组进行维护查询的操作,比较常见的如,修改某点的值、求某个区间的和,而这两种恰恰是树状数组的强项!当然,数据规模不大的时候,对于修改某点的值是非常容易的,复杂度是O(1),但是对于求一个区间的和就要扫一遍了,复杂度是O(N),如果实时的对数组进行M次修改或求和,最坏的情况下复杂度是O(M*N),当规模增大后这是划不来的!而树状数组干同样的事复杂度却是O(M*lgN),别小看这个lg,很大的数一lg就很小了,这个学过数学的都知道吧,不需要我说了。

二、原理讲解


给定序列(数列)A,我们设一个数组C满足

C[i] = A[i–2^k+ 1] + … + A[i]

其中,k为i在二进制下末尾0的个数,i从1开始算!

则我们称C为树状数组。

下面的问题是,给定i,如何求2^k?

答案很简单:2^k=i&(i^(i-1)) ,也就是i&(-i)    为什么呢?? 请看下面:

整数运算 x&(-x),当x为0时结果为0;x为奇数时,结果为1;x为偶数时,结果为x中2的最大次方的因子。
       因为:x &(-x) 就是整数x与其相反数(负号取反)的按位与:1&1=1,0&1 =0, 0&0 =1。具体分析如下:
       □ 当x为0时,x&(-x) 即 0 & 0,结果为0;
       □ 当x不为0时,x和-x必有一个为正。不失一般性,设x为正。
       ●当x为奇数时,最后一个比特为1,取反加1没有进位,故x和-x除最后一位外前面的位正好相反,按位与结果为0。最后一位都为1,故结果为       1。
       ●当x为偶数,且为2的m次方(m>0)时,x的二进制表示中只有一位是1(从右往左的第m+1位),其右边有m位0,左边也都是0(个数由表示   x的字        节数决定),故x取反加1后,从右到左第有m个0,第m+1位及其左边全是1。这样,x& (-x) 得到的就是x。 
       ●当x为偶数,却不为2的m次方的形式时,可以写作x= y * (2^k)。其中,y的最低位为1。实际上就是把x用一个奇数左移k位来表示。这时,x的   二进制         表示最右边有k个0,从右往左第k+1位为1。当对x取反时,最右边的k位0变成1,第k+1位变为0;再加1,最右边的k位就又变成了0,第   k+1位因为进         位的关系变成了1。左边的位因为没有进位,正好和x原来对应的位上的值相反。二者按位与,得到:第k+1位上为1,左边右边都为   0。结果为2^k,即         x中包含的2的最大次方的因子。
        总结一下:x&(-x),当x为0时结果为0;x为奇数时,结果为1;x为偶数时,结果为x中2的最大次方的因子。 比如x=32,其中2的最大次方因子  为                 2^5,故x&(-x)结果为32;当x=28,其中2的最大次方因子为4,故x & (-x)结果为4。当x=24,其中2的最大次方因子为8,故 x&(-x)结果为  8。

(结合网址:http://blog.csdn.net/int64ago/article/details/7429868)

三、用途

(1)最基本的数组区间改变、更新、求和。

(2)逆序数

(3)二维线段树求矩阵的更新、求和等(暂未整理)


四、经典题型(详情:http://blog.csdn.net/zhengxu001/article/details/8029790)


源代码模板:

#include<stdio.h>#include<string.h>#include <iostream>using namespace std;#define MAX 100int a[MAX],c[MAX];int lowbit(int x){    return x & (-x);}void update(int x,int add){//更新树状数组   x为原数组的下标  add是原数组的值     while(x<MAX){//更新范围,如果数组没全部用到不用全部更新,条件可以设置         c[x]+=add;            x+=lowbit(x);    }}int get_sum(int x){//求前n项和     int sum=0;     while(x!=0){               sum+=c[x];           x-=lowbit(x);    }      return sum;}int main (){int a[]={0,1,2,3,4,5,6,7,8,9,10};for(int i=1;i<=10;i++)   update(i,a[i]);   for(int i=1;i<=100;i++)   cout<<c[i]<<' ';cout<<endl<<endl;    for(int i=1;i<=100;i++)     cout<<get_sum(i)<<' ';return 0;}


0 0