树状数组基本概念

做POJ有关逆序对的题时得知了有树状数组这一大杀器,赶快学习了一下
树状数组如图:
这里写图片描述
由上图可以看出树状数组跟二分很相似,将一个数组转换成了一个树(话说怎么想到的?这算法实在膜拜),因此将复杂度降低到了nlogn。
C[n]可以由前面的C数组之和来求出,那么是怎么算的呢?这里的计算都是以二进制为基础。

首先是有一个lowbit函数,lowbit(k)就是把k的二进制的高位1全部清空,只留下最低位的1,比如10的二进制是1010,lowbit(k)=lowbit(1010)=0010(2进制),介于这个lowbit在下面会经常用到,这里给一个非常方便的实现方式,比较普遍的方法lowbit(k)=k&-k,这是位运算,我们知道一个数加一个负号是把这个数的二进制取反+1,如-10的二进制就是-1010=0101+1=0110,然后用1010&0110,答案就是0010了!

lowbit函数就是为了联系a数组和c数组的!c[k]表示从a[k]开始往左连续求lowbit(k)个数的和,比如c[0110]=a[0110]+a[0101],就是从0110开始计算了0010个数的和,因为lowbit(0110)=0010(=2),可以看到其实只有低位的1起作用,因为很显然可以写出c[0010]=a[0010]+a[0001],这就为什么我们任何数都只关心它的lowbit,因为高位不起作用(基于我们的二分规则它必须如此!),除非除了高位其余位都是0,这时本身就是lowbit。
举例:C[6]=a6+a5, 6的二进制是0110,补码为1010,根据按位与运算,都为1的地方为1,其余地方为0,得到lowbit[6]=0010(=2),所以C[6]为从a6向左2个数的和,即a6+a5。

基本的思路就是上面的了,这里我们还看不出树状数组的优越性,下面举两个最基本的应用就可以看出这种算法的强大:
一:求区间和运算:
当想要查询一个sum(n)(求a[n]的和),可以依据如下算法即可:
step1: 令sum = 0,转第二步;
step2: 假如n <= 0,算法结束,返回sum值,否则sum = sum + C[n],转第三步;
step3: 令n = n – lowbit(n),转第二步。
举例:如果要求sum[6],首先sum[6]=0+C[6],然后n=n(6)-lowbit(6)(2)=4,得到sum[6]=0+C[6]+C[4],继续n=n(4)-lowbit(4)(4)=0,输出sum[6]=C[6]+C[4]

int read(int k)//1~k的区间和  
{  
    int sum=0;  
    while(k)  
    {  
        sum+=tree[k];  
        k-=k&-k;  
    }  
    return sum;  
}  

二:修改运算:
修改一个节点,必须修改其所有祖先,最坏情况下为修改第一个元素,最多有log(n)的祖先。
所以修改算法如下(给某个结点i加上x):
step1: 当i > n时,算法结束,否则转第二步;
step2: C[i] = C[i]+ x, i = i + lowbit(i)转第一步。
i = i +lowbit(i)这个过程实际上也只是一个把末尾1补为0的过程。
举例:要修改C[4],先C[4]+=x,i=i(4)+lowbit(i)(4)=8,继续C[8]+=x,
i=i(8)+lowbit(8)(8)=16,如果n=8~15,算法结束

void add(int k,int num)  
{  
    while(k<=n)  
    {  
        tree[k]+=num;  
        k+=k&-k;  
    }  
}  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值