什么是树状数组?
是用一种类似于二叉树的森林结构来模拟树形结构,顾名思义就是用数组模拟树形结构。
这是一个可以让算法的时间复杂度下降至与n转化成二进制数中的“1”的有关。
为什么不直接建树?
当然是因为它具有简便性,能用树状数组就不建树
树状数组的用途是?
它的基本用途是维护序列的前缀和。
简单来说就是可以用于求区间和,查询数,更新数等。
树状数组的结构?
这里介绍一种lowbit运算,lowbit(n)定义为非负整数n在二进制表示下“最低位的1及其后面所有的0”构成的数值。
通俗地讲,lowbit就是求数n的二进制中从低位开始,拢共有多少个连续0。
给定x,按二进制表示为:
x= 2^k +2^(k-1)+2^(k-2)+……+2^1+1;
那么x就能划分成以下区间:
(x-1 , x]
(x - 1 - 2^1, x-1 ]
(x - 1 - 2^1 - 2^2 , x - 1 - 2^1 ]
(x - 1 - 2^1 - 2^2 -2 ^3, x - 1 - 2^1 - 2^2 ]
………………
(0,x - 1 - 2^1 - 2^2 -2 ^3 -……- 2 ^ (k-1) ]
不难发现
这些区间,若区间的结尾数为r,则区间长度就是r的“二进制数”的最小2的次幂,即为lowbit(r).
且分成的区间数是log(n)的。
所以基于上述,若给定一个序列a,那建立一个c数组,
其中c[x]是保存a序列的 [ x - lowbit(x) +1, x ]中所有数的和。
举个栗子:
x=15=1111(二进制)
1~15的区间和sum=c[15]+c[14]+c[12]+c[8];
图示总结
![](https://i-blog.csdnimg.cn/blog_migrate/374043f1aa9caeec9b766c749f078eb8.png)
![](https://i-blog.csdnimg.cn/blog_migrate/1bedecad164824cfaeec5afc5a2b618a.png)
重要的代码表示
lowbit函数不是系统自带的,需要手写
int lowbit(int x){
return x&(-x);
}
给定整数x,求[1,x]分成的log (n)个小区间
while(x){
printf("[%d, %d]\n",x-lowbit(x)+1,x);
x-=lowbit(x);
}
基本操作之-----在O(log n)的时间内查询区间和
int ask(int x){
int ans=0;
while(x){
ans+=c[x];
x-=lowbit(x);
}
return ans;
}
基本操作之-----单点增加(即给数列中a[i]加上一个数,并且维护序列的前缀和)
void add(int x,int y){//y是要加上的数
while(x<=n){//n是数列长度
c[x]+=y;
x+=lowbit(x);
}
}
下面是一道板子题
这与逆序对问题差不多
我们需要建立树状数组(初始化为零),进行单点增加操作就把a[i]上的数加1即可(意味数值a[i]又出现了一次)。在这就不过多解释
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1e6+10;
LL a[N],c[N];
int zx[N],zd[N],yx[N],yd[N];
int n;
LL lowbit(int x){
return x&(-x);
}
void gx(int x,int y){
while(x<=n){
c[x]+=y;
x+=lowbit(x);
}
}
LL qh(int x){
LL ans=0;
while(x){
ans+=c[x];
x-=lowbit(x);
}
return ans;
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
zx[i]=qh(a[i]);
zd[i]=i-1-zx[i];
gx(a[i],1);
}
memset(c,0,sizeof c);
for(int i=n;i>=1;i--){
yx[i]=qh(a[i]);
yd[i]=n-i-yx[i];
gx(a[i],1);
}
LL ans1=0,ans2=0;
for(int i=1;i<=n;i++){
ans1+=(LL)zd[i]*yd[i];
ans2+=(LL)zx[i]*yx[i];
}
cout<<ans1<<" "<<ans2;
return 0;
}
树状数组的区间增加?
若给定数组a,求将数列中的数l~r加上d
关于区间增加:
如果像上述一般查询,面子时间上肯定过不去
建一个数组b,对于每条指令,把b[l],加上d,b[r+1]减去d(差值)
不难发现,b数组的前缀和反映l指令对a数组的影响。那么就可以用树状数组来维护数组b的前缀和b[1~x],就得出a数组上增加的数值总和。
假设我们规定b[0] = 0;
某个区间[x,y]值改变了,区间内的差值是不变的,只有D[x]和D[y+1]的值发生改变
根据图
![](https://i-blog.csdnimg.cn/blog_migrate/72a218f97e572e9dd2be4a64e1645122.png)
那么来个板子题吧