树状数组

模板题:
有一个int型数组arr[],并且多次修改数组中的数字,求数组某个区间中所有数字的和。
模板代码:

int arr[n];//原数组
int c[n];  //树状数组
int lowbit(int i){
    return i & -i;
}

void update(int x,int y){//更新x位置的值,与原值差值为y
    while(x<=n){
        c[x]+=y;
        x+=lowbit(x);
    }
}
int sum(int i){//查询区间[1,i]
    int ret=0;
    while(i>0){
        ret+=c[i];
        i-=lowbit(i);
    }
    return ret;
}

让我们依次从上往下看来理解这份代码:

1.int arr[n]
这是我们要进行的操作的数组,由用户给出,这个就不需要多讲了。

2.int c[n]
这是我们根据原数组构造出来的树状数组,它的赋值由后面的更新函数updata()实现。

3.int lowbit(int i)
树状数组实现中最重要的一环,这函数实现了找到数字i在二进制下最末位1对应的数值。举个例子:

但这个函数的代码只有一行,它是怎么实现的呢?
首先,我们要对整数的二进制有一定的了解:
1)整数i的反码为-i(连符号位一起变);
2)求整数i的反码:写出二进制下的i,从右往左找到第一个1,这个1和其右边的0都不要动,左边的数取反,得到的数就是i的反码。
基于上面的原理,可得到下面的表格:

i-ii&-i
100010B011110B10B
111000B001000B1000B

看,经过上面的推理,我们就得到了一行代码实现了找到数字i在二进制下最末位1对应的数值的结果。
OK,知道lowbit()这个函数的作用和实现了,我们看下一个函数。

4.void update(int x,int y)
这个函数用于修改更新树状数组c[]中的值。
我们先来看看我们要实现的树状数组c[]的结构:
在这里插入图片描述
在这张图中,绿色方块代表C[i]的值,和它连接的紫色部分代表它下包的arr[]数组的数值,即有:

c[1]=arr[1]
c[2]=arr[1]+arr[2]
c[3]=arr[3]
c[4]=arr[1]+arr[2]+arr[3]+arr[4]
c[5]=arr[5]
c[6]=arr[5]+arr[6]
c[7]=arr[7]
c[8]=arr[8]
......

了解了c[]数组的结构,我们再返回来看函数update()是怎么实现这个结构的。函数的参数x代表更新数字的位置,y代表更新数字与原来数字的差值。
根据c[]数组的结构,当我们更新了c[3]的值,那c[4],c[8],c[16]…的值也会更新,所以问题就变成了寻找 3,4,8,16… 这个序列的规律。然后我们就可以发现(其实真的很难发现,还是得看代码才知道):

x=3,                                 c[3]+=y
3+lowbit(3)=4 ,                      c[4]+=y
4+lowbit(4)=8,                       c[8]+=y
8+lowbit(8)=16,                      c[16]+=y
......

上述式子等价于:

 int x=3,y=差值;
 while(x<=n){
        c[x]+=y;
        x+=lowbit(x);
    }
}

通过这种向上更新的方法,我们就能以 O(logn) 的复杂度更新数组区间和了。

5.int sum(int i)
继续看那张c[]数组结构图,如果我们想查询区间[1,8],我们只需要得到c[8]就行,但如果想查询区间[1,9],我们就需要得到c[8]+c[9]了,而[1,11]则更复杂,我们要得到c[8]+c[10]+c[11]。与更新函数相似,查找[1,11]变成了寻找序列 11,10,8… 的规律。然后我们就可以发现(其实真的很难发现,还是得看代码才知道):

 x=11				           ret+=c[11]
 11-lowbit(11)=10               ret+=c[10]
 10-lowbit(10)=8	   	           ret+=c[8]
 8-lowbit(8)=0		           跳出

上述式子等价于:

int x=11,ret=0;
while(x>0){
        ret+=c[x];
        x-=lowbit(x);
}

这样我们就找到了区间[1,x]的和,当我们要查询区间[left,right]的时候,我们只要sum(right)-sum(left-1)就行。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值