树状数组(树状数组的基本用法与操作)
什么是树状数组?树状数组简单的来说就是将一个数组模拟树形结构。
树状数组有什么用?树状数组可以将求和的操作从O(n)操作简化为O(logn)。
如图所示,横线下方为a数组表示为初试数据;上方为数组c,利用树形结构存储a数组内的数据。我们列举出来的这些:
c[1]=a[1]
c[2]=a[1]+a[2]
c[3]=a[3]
c[4]=a[1]+a[2]+a[3]+a[4]
c[5]=a[5]
c[6]=a[5]+a[6]
c[7]=a[7]
c[8]=a[1]+a[2]+a[3]+a[4]+a[5]+a[6]+a[7]+a[8]
......
当我们列举出来这些数据的时候,我们发现:
①所有下标为奇数的c数组都为对应的a数组的值。
②所有下标为2的k次幂的c数组都为对应a数组2的k次幂的前缀和。
③其它下标为偶数的c数组都是由该偶数前所有的值的和,减去该下标对应的最大的2的k次幂的值的和。
即c[i] =a[i - +1] + a[i -
+2] + ... + a[i]; //k为i的二进制中从最低位到高位连续零的长度(该偶数的2进制数的最低位1的位置再-1。
当我们计算7的前缀和,即我们需要计算sum=c[7]+c[6]+c[4];
即我们可以拓展为SUM = C[i] + C[i-] + C[(i -
) -
] + .....;//k1k为i的二进制中从最低位到高位连续零的长度,k2为i-
后从最低位到高位连续零的长度。
我们引入lowbit(x)函数,此函数操作为x&(-x)。这里的-x在计算机中是以补码的形式计算的。此函数的作用为:当x为0时,返回为0;当x为奇数时,返回1;当x为偶数时,且为2的m次方时,返回x;当x为偶数,却不为2的m次方的形式时,返回,其中k为二进制中从最低位到高位连续零的长度(该偶数的2进制数的最低位1的位置再-1)。
注意事项:树状数组能够有效的解决单点更新,区间查询的过程。
题目引入:树状数组
代码:
#include<iostream>
#include <stdio.h>
#include<string.h>
using namespace std;
int a[1000001];
int c[1000001];
int lowbit(int i){
return i&(-i);
}
void update(int i,int x,int n){
while(i<=n){
c[i]+=x;
i+=lowbit(i);
}
}
int downdate(int i){
int sum=0;
while(i>0){
sum+=c[i];
i-=lowbit(i);
}
return sum;
}
int main(){
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
update(i,a[i],n);
}
int x,y,z;
for(int i=1;i<=m;i++){
cin>>x>>y>>z;
if(x==1)update(y,z,n);
else cout<<downdate(z)-downdate(y-1)<<endl;
}
return 0;
}
区间更新,单点查询
我们使用差分数组来表示,即D[i]=a[i]-a[i-1](i>=1);//建议看这里,有比较详细的介绍和说明
区间更新:
例如我们需要更新区间[2,6],将[2,6]区间内的每一个都加上x,x可正可负;
则:
D[2]=(a[2]+x)-(a[1]) =a[2]-a[1]+x=D[2]+x
D[3]=(a[3]+x)-(a[2]+x)=a[3]-a[2] =D[3]
D[4]=(a[4]+x)-(a[3]+x)=a[4]-a[3] =D[4]
D[5]=(a[5]+x)-(a[4]+x)=a[5]-a[4] =D[5]
D[6]=(a[6]+x)-(a[5]+x)=a[6]-a[5] =D[6]
D[7]=(a[7]) -(a[6]+x)=a[7]-a[6]-x=D[7]-x
即若更新[2,6],将区间[2,6]区间内的每一个都加上x,则就相当于D[2]+x,D[7]-x。
我们推广到一般情况,如果更新区间[a,b],且将[a,b]区间内加上x,则相当于D[a]+x,D[b+1]-x。
单点查询:
若想查询a[n],则根据累加法:
a[n]=D[n]+a[n-1]
a[n-1]=D[n-1]+a[n-2]
...
a[3]=D[3]+a[2]
a[2]=D[2]+a[1]
a[1]=D[1]+a[0]
a[0]=0
则
这种情况我们则需要用树状数组了。
即用D数组来建立一个树状数组。
区间更新,区间查询
对于求[1,n]区间内的总和sum,我们有如下处理:
已知则
=n*(D[1]+D[2]+...+D[n])-(0*D[1]+2*D[2]+...+(n-1)*D[n])
=
这样我们得到了两个,即可用于树状数组。
我们令c数组为(i-1)*D[i]。
所以,我们设置两个树状数组sum1[i]<-D[i]和sum2[i]<-c[i]。
区间更新:
若更新区间[a,b],并且在区间同时+x,对于sum1,我们可以在a处向上更新x,在b+1处更新-x;对于sum2,我们可以在a处向上更新(a-1)*x在b+1处向上更新b*-x;
区间查询:
若查询区间[l,r],我们可以找到区间[0,l-1]的值,向下查找sum1-sum2,找到[0,r],向下查找sum1-sum2;
代码:
#include<bits/stdc++.h>
int n,m;
int a[50005] = {0};
int sum1[50005]; //(D[1] + D[2] + ... + D[n])
int sum2[50005]; //(1*D[1] + 2*D[2] + ... + n*D[n])
int lowbit(int x){
return x&(-x);
}
void updata(int i,int k){
int x = i; //因为x不变,所以得先保存i值
while(i <= n){
sum1[i] += k;//更新sum1
sum2[i] += k * (x-1);//更新sum2
i += lowbit(i);
}
}
int getsum(int i){ //求前缀和
int res = 0, x = i;
while(i > 0){
res += x * sum1[i] - sum2[i];//这里因为sum1没有乘以n,所以在出结果时成
i -= lowbit(i);
}
return res;
}
int main(){
cin>>n;
for(int i = 1; i <= n; i++){
cin>>a[i];
updata(i,a[i] - a[i-1]); //输入初值的时候,也相当于更新了值
}
//[x,y]区间内加上k
updata(x,k); //A[x] - A[x-1]增加k
updata(y+1,-k); //A[y+1] - A[y]减少k
//求[x,y]区间和
int sum = getsum(y) - getsum(x-1);
return 0;
}
当然,对于区间查询,区间更新的问题,我么还可以使用线段树来处理。
树状数组求逆序对数
我们求逆序对数除了归并算法求,还可以树状数组求逆序对数。
步骤:
- 离散化:这里所谓离散化就是将一个序列的相对大小表示出来。目的是为了方便数据存储。对于重复的数据,我们就依据数据的先后顺序来处理
例如序列:6 25 9 63 2
序列的离散化结果为2 4 3 5 1
例如序列5 60 5 3 2 3
序列的离散化结果为4 6 5 2 1 3
- 树状数组求逆序对数:
我们怎样求逆序对数呢?
我们可以依次从后向前遍历,以遍历的时间为序列的顺序,向前查找并且更新。
例如此序列逆序为1 5 3 4 2,则第一次寻找1前面的数据有多少比1小(向下寻找),然后把1向上更新+1;第二次寻找5前面的数据,发现c[4]=1,即5前面有1个比5小的数,然后将5向上更新+1。
题目引入:树状数组求逆序对
代码:
#include<bits/stdc++.h>
using namespace std;
struct node{
long long sum;
long long j;
long long k;
};
long long sum=0;
node a[50000005];
long long c[50000005];
long long lowbit(long long x){
return x&(-x);
}
bool cmp(const node xx,const node yy){
if(xx.sum==yy.sum&&xx.j<yy.j)return true;
if(xx.sum<yy.sum)return true;
else return false;
}
bool cmp1(const node xx,const node yy){
if(xx.j<yy.j)return true;
else return false;
}
int main(){
long long n;
cin>>n;
for(long long i=1;i<=n;i++){
cin>>a[i].sum;
a[i].j=i;
}
sort(a+1,a+1+n,cmp);
for(long long i=1,x=1;i<=n;i++){
a[i].k=x;
x++;
}
sort(a+1,a+1+n,cmp1);
for(long long i=n;i>=1;i--){
long long x=a[i].k;
long long y=x;
while(y>0){
sum+=c[y];
y-=lowbit(y);
}
while(x<=n){
c[x]++;
x+=lowbit(x);
}
}
cout<<sum;
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话