树状数组总结

树状数组看了很久,因为看到的文章几乎都是告诉我是什么,而没有告诉我为什么。这篇文章集合我看过的所有资料加上我自己的思考。

先讲知识点再讲理论逻辑吧,不然脑袋里没图,云里雾里。

先来讲个函数:lowbit(n)

它的作用就是获得非负整数n在二进制表示下最低位1及其后面的0构成的数值
如,二进制为101100的44,lowbit(44)就获得二进制数100,也就是十进制的4
lowbit(n)=n&(~n+1)=n&(-n)

然后,看两个操作:

①将数组中的第x个数加上k
②输出区间[x,y]内每个数的和

第一个贼简单,直接加就是对吧。那第二个一看就是要用前缀和。也没什么难的嘛。but是这两个操作都要实现。

当你不搞前缀和,就可以第一个操作直接加。但是第二个操作的时间复杂度就很高。
当你搞了前缀和,第二个操作就很快。但是第一个操作就很麻烦。每改变一个数,这个数之后的所有数都要更新一遍。

所以现在要避免某一个操作效率差的情况。
首先原来的数组是一定不行的。肯定要对数组做点什么。可是又不能用前缀和。

那我们来想想。为什么前缀和数组效率差呢,还不是因为它们的关联性太强了。每改变一个数,这个数之后的所有数都要更新一遍。简直是牵一发而动全身。

回想一下数据结构,最简单的链表和数组,关联性都很强。但是树不一样,树的一个节点改变,最多影响它的父节点。不影响其它节点。

树的结构再加上二进制就是我们讲的树状数组
上一张二进制的图,这张图是b站上鹤翔万里up主的视频截图

在这里插入图片描述
可以看到,对于位置i,这棵树的第一层结点为全体
在这里插入图片描述
即所有lowbit(i)=1
第二层结点为全体
在这里插入图片描述
即所有的lowbit(i)=2

(以上黑体字是知乎上SleepyBag大佬的解释,我觉得他讲得非常清晰明了)

再来看遍图
在这里插入图片描述
那么这棵树的两个特点也就出来了。第一个就是上面的黑体字,另一个特点就是每个节点保存它所有的子节点的和。

因为比如1010(二进制)加上它的lowbit=2,也就是加上二进制的10(1010的最低位)
1010+10=11000,它的lowbit就升了一级。所以
t[x]节点的父节点为t[x+lowbit(x)]

根据图可知,每个节点t[x]的长度等于lowbit(x),所以每个t[x]保存的是它的下标x减去它的长度再加一开始的数组元素一直到a[x],配合图再理解下面的公式。
在这里插入图片描述
现在说说树状数组的基本运用
①修改某一点的值,add(x,k)操作
因为一个节点更改,它只影响它的父节点们。那就一层一层往上改呗。
这是鹤翔千里前辈的视频截图
在这里插入图片描述
代码如下:

void add(int x,int k){
	for(;x<=n;x+=lowbit(x) t[x]+=k;
}

②询问第x个元素的前缀和,ask(x)操作
在这里插入图片描述
看图我们可以知道,要求x点的前缀和,就需要往一层一层加左上的点。

第一层的点就是因为它的lowbit比第二层的lowbit小,也就是第一层比第二层多了低位的1。那么把这个低位的1减去,就得到了上一层的点。

比如7(0111)就是比6(0110)多了个最低位的1,把这个最低位1减去就是6了。

两层之间的差异自然只有一个lowbit,所以往左上找上一个节点,只需要将下标-=lowbit(这个节点的下标)

代码如下:

int ask(int x){
	int ans=0;
	for(;x;x-=lowbit(x))ans+=t[x];
	return ans;
}

另外,如果哦需要求区间和,我们可以分别求出前缀和并相减

要求某一个点的值可以ask(k)-ask(k-1)

树状数组主要是用于动态维护前缀和。比起算法,它更像一种工具。

树状数组的知识理论就讲到这里吧,这篇文章的图片和代码都是B站上鹤翔万里前辈的,大家也可以去找找他的视频看看,讲得非常好。我的文章主要是从原理和细节处做了很多自己的理解和思考。下篇文章讲一下树状数组的应用吧,小朋友排队的那道题。过几天应该会写吧…orz

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值