树状数组 从入门到不会(超详细)

树状数组

这是一种基于二进制思想的数据结构,他的主要功能是维护前缀和,支持修改与求和两种操作(复杂度都是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[ilowbit(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] 他求出了 [ilowbit(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=1ij=1cj

观察后发现,c[1]出现了n次,c[k]出现了(nk+1)

所以

ni=1Ai=ni=1(ci(n+1)cii)

所以我们只需要维护两个树状数组,一个是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);
//求修改后区间和

 

看完留个赞呗 逃

 

posted @ 2022-02-15 18:51  Xu_brezza  阅读(70)  评论(0编辑  收藏  举报