树状数组 从入门到不会(超详细)
树状数组
这是一种基于二进制思想的数据结构,他的主要功能是维护前缀和,支持修改与求和两种操作(复杂度都是O(nlogn)),这其中又分为单点修改,区间修改,区间查询,本文会依次介绍。
入门
对于一个数,我们可以用他的二进制表示来把他拆成几个小区间,以(10101)为例,它可以拆成 24+22+20 所以就可以得到三个区间分别为 [1,24][24+1,24+22][24+22+1,24+22+20]
进而我们可以知道,对于查询一个结尾为 i 的区间,得到的区间长度就是二进制下他最小的2的幂,定义为 lowbit(i) 要想求出他也很简单,就是 i & −i ,因为计算机中负数使用的是补码表示法,也就是按位取反再加一,这会使原来的0变成1,1变成0,然后加一又会使原来低位的0重新变回0,直到原来最低位的1重新变回1,故而得到了lowbit 不理解可以手推一遍。
到此,我们可以建立一个树状数组,c[i] 存储了给定序列A中 [i−lowbit(i)+1,i]区间的元素和,也就是以i 为根节点的子树的叶子结点的和,也不难发现,该节点的父亲节点为 c[i+lowbit(i)] ,他有 lowbit(i)个叶子节点。
附上我们构造出的数组结构图片,就能理解为什么叫树状数组了
百度yyds
单点修改
这颗数的深度是log的,因此对每个元素的修改操作,在树状数组中只需要 log的修改,是不是很棒呢。
我们只需要不断寻找当前节点的父亲节点,也就是加上lowbit(i) 并给他们也加上这个元素,就可以啦
inline void update(int i,int x){ for(;i<=n;i+=lowbit(i))c[i]+=x; }
c是我们的树状数组,i是原序列修改的点,x是加上的数。
查询区间前缀和
我们可以发现,对于当前的c[i] 他求出了 [i−lowbit(i)+1,i]区间的元素和,那么我们只需要每次减掉lowbit(i)就可以得到下一个大区间的和,累加即可得到前缀和,复杂度显然也是O(logn)的
inline int sum(int i){ int ans = 0; for(;i;i-=lowbit(i))ans+=c[i]; return ans; }
完结撒花~~~
恭喜你,看到这里已经成功入门了树状数组。
接下来我们介绍对于他的升级:
区间修改:对给定区间同时加减一个数k & 单点查询:查询修改后给定位置元素的值
乍一看是不是感觉会非常非常非常非常非常非常麻烦。
不过我们有个好东西,叫差分数组,如果还不了解的话请自行百度。
众所周知,差分与前缀和是互逆操作,说成人话就是:差分的前缀和就是原数列A,前缀和的差分也是原数列A。
看到这里是不是有点眉目了?
是的,在这里我们的差分数组不再是原数列A的前缀和,我们可以先引入一个数组d,A′[i]=d[i]+A[i] 也就是说,d[i]记录了A[i]被修改的值。
显然, d的初始值为0。
那么考虑d的差分数组,记为B ,那么我们只需要求出B的前缀和再加上A[i],就能得到A′[i],也就是修改后的值。
这时我们的树状数组c维护的就是B的前缀和了,修改区间[x,y]的操作只需要update(x,k)update(y+1,−k) 即可
查询修改后的元素值就是 sum(i)+A[i]
究极进化 : 区间修改后的区间查询
这次我们考虑对原数组A差分, 记为 a 吧 ,那么树状数组维护的就是a的前缀和,也就是说 An=∑ni=1ci
修改操作也是和原来的一样,update(x,k)update(y,−k)即可 那么如何查询区间和呢?
不难发现有如下表达式∑ni=1Ai=∑ni=1∑ij=1cj
观察后发现,c[1]出现了n次,c[k]出现了(n−k+1)次
所以
∑ni=1Ai=∑ni=1(ci∗(n+1)−ci∗i)
所以我们只需要维护两个树状数组,一个是c[i] 一个是c[i]∗i
inline void update(int i,int x){ for(;i<=n;i+=lowbit(i))f[i]+=x; }//更新f[i] -> c[i] inline int sum(int *tree,int x){ int ans = 0; for(;x;x-=lowbit(x))ans+=tree[x]; return ans; }//统一求和 inline void update1(int i,int x){ int j = i; for(;i<=n;i+=lowbit(i))c[i]+=x*j; }//更新c[i] -> c[i] * i int s1 = (y+1)*sum(f,y) - sum(c,y); int s2 = x*sum(f,x-1) - sum(c,x-1); printf("%d\n",s1-s2); //求修改后区间和
看完留个赞呗 逃