树状数组
树状数组
利用二进制的一些性质使得区间修改、区间查询效率达到log的数据结构
-
lowbit
lowbit(x)求出二进制x中末尾的1
公式:lowbit(x)=x&-x;
举个例子:
计算时用补码,正数的补码是其原码,负数的补码是反码+1 14(00001110) &-14(11110010) _______________ 00000010
-
特殊性质
树状数组的节点c[i]一般用来表示a[i-lowbit(i)+1]到a[i]的和,c[i]的父亲节点为c[i+lowbit(i)]
显然一个节点并不一定是代表自己前面所有元素的和。只有满足 2^n这样的数才代表自己前面所有元素的和。
- 操作
1.单点修改
假设修改a[x],由于树状数组要修改若干个连续数的和,修改了a[x]之后,c[x]--c[n]可能也会发生变化,因此要向上更新。
假设a[x]增加了x,则sum[x]-- sumn均增加了x,所以只要把增量向上更新即可。
void update(int id,int x){//点的编号 增量
while(id<=n){
c[id]+=x;
id+=lowbit(id);//到达父亲节点
}
}
2.单点查询
假设要求出a[1]---a[x]的和,根据lowbit的性质,a[x]的子节点为a[x-lowbit(x)],一直向下查询,直到x减到零为止
int query(int x){
int res=0;
while(x){
res+=c[x];
x-=lowbit(x);
}
return res;
}
3.区间修改单点查询
引入一插值数组b,定义b[i]=a[i]-a[i-1],用b[i]建立树状数组。对于每一个a[i],都有a[i]=b[1]+b[2]+...+b[i],前缀和查询就相当于单点查询
设要求把x--y加上data,则x--y之间的差值不变,1--x-1之间的差值不变,y+2--n之间的差值不变,b[x]=a[x]+data-a[x-1],b[y+1]=a[y+1]-(a[y]+data),只需要把这两个值更新即可
易错点
设要求把x--y加上data,不能写成update(x,data);update(y+1,data)(这里树状数组维护的原数组),这样只是修改了两个端点,中间的值没有修改
4.区间修改区间查询
sum[i]=a1+a2+a3+a4+..+a[i]
=b[1]+(b[1]+b[2])+(b[1]+b[2]+b[3])+...+(b[1]+b[2]+b[3]+...+b[i])
=i * b[1]+(i-1)* b[2]+(i-2)* b[3]+...+2 * b[i-1]+b[i]
=i * (b[1]+b[2]+b[3]+...+b[i])-(0 * b[1]+1 * b[2]+2 *b [3]+...+ (i-1) * b[i])
用一个数组c1维护b[i],另一个c2维护(x-1)*b[i]
答案为i* query(i,c1)-query(i,c2)
void update(int x,long long data){
int i=x;
while(i<=n){
c[i]+=data;
c2[i]+=(x-1)*data;
i+=lowbit(i);
}
}
long long query(int x){
long long ans=0;
int i=x;
while(i>0){
ans+=c[i]*x-c2[i];
i-=lowbit(i);
}
return ans;
}
二维树状数组
c[i][j]维护以(i,j)为左下角的宽为lowbit(i),长为lowbit(j)的矩阵内元素之和(具体视情况)
1.单点修改,区间查询
void update(int x,int y,int data){
for(int i=x;i<=n;i+=lowbit(i)){
for(int j=y;j<=m;j+=lowbit(j)){
c[i][j]+=data;
}
}
}
int query(int x,int y){
int ans=0;
for(int i=x;i;i-=lowbit(i)){
for(int j=y;j;j-=lowbit(j)){
ans+=c[i][j];
}
}
return ans;
}
假设要求左下s角坐标为(a,b)到右上角(c,d)矩阵的和,令sum[i][j]表示(1,1)到右上角为(i,j)矩阵元素之和,则所求矩阵元素和为sum[c][d]-sum[a-1][d]-sum[c][d-1]+sum[a-1][b-1]
即ans=query(c,d)-query(c,b-1)-query(a-1,d)+query(a-1,b-1);
2.区间修改单点查询
仍要引入一差分数组。
由于数组a[i][j]的前缀和有这样的公式:
sum[i][j]=(sum[i−1][j]+sum[i][j−1]−sum[i−1][j−1])+a[i][j]
套用求前缀和的形式,假设差分数组为d[i][j],可以有:
a[i][j]=(a[i−1][j]+a[i][j−1]−a[i−1][j−1])+d[i][j]
就能得到:
d[i][j]=a[i][j]−(a[i−1][j]+a[i][j−1]−a[i−1][j−1])
假设要给(x1,y1)到(x2,y2)的矩阵全部加上x,差分数组变动的只有:
d[x1][y1]+=x,d[x1][y2+1]-=x,d[x2+1][y1]-=x;d[x2+1][y2+1]+=x;
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了