树状数组
树状数组总结
前言
树状数组是数据结构中的一股清流,代码简洁,思路清晰,又好理解 qwq。
前置芝士
lowbit:https://www.cnblogs.com/zhouruoheng/p/18003331
简介
树状数组是一种基于 lowbit 的用于维护
支持:
- 快速求前缀和,复杂度为
。 - 修改某一个数,复杂度为
。
可以看下表:
数组 | 前缀和数组 | 树状数组 | 线段树 | |
---|---|---|---|---|
单点修改 | ||||
查询 |
树状数组将各操作优化成
具体实现
存储
树状数组具体实现和 st 表差不多,尤其是区间划分。
首先,一个数可以用二进制表示:
设
长度分别为
它们有个共同特点:若区间右端点为
那么设存有
如下图:
查询
进行查询前缀和那么就要将原区间分解成若干小区间,就和分解正整数那样,然后相加。
在上面的处理出
code:
int sum(int x)
{
int ans=0;
for(int i=x;i;i-=lowbit(i)) ans+=c[i];
return ans;
}
修改
既然要进行单点修改,那么儿子一定会影响其父亲,因此我们必须修改所以有影响的点。由图可知,每个点都只有一个父亲,只需要一直往父亲那走就好了。那么父节点和子节点有什么关系呢?
显而易见,节点
所以只要一直往父节点去,然后进行修改就可以了。
// 将a[x]加上y
void add(int x,int y)
{
for(int i=x;i<=n;i+=lowbit(i)) c[i]+=y;
}
初始化
有了修改操作后,那么就可以非常简单的进行初始化,将
void init()
{
for(int i=1;i<=n;i++) add(i,a[i]);
}
初始化还有一种更快的方法,时间复杂度为线性。先处理出前缀和,再直接给
int b[N];//a 的前缀和
void init()
{
for(int i=1;i<=n;i++)
{
b[i]=b[i-1]+a[i];
c[i]=b[i]-b[i-lowbit(i)];//[i-lowbit(i)-1,i]
}
}
例1 P3374 【模板】树状数组 1
https://www.luogu.com.cn/problem/P3374
分析
将上面所讲的内容结合一下即可。
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5e5+5;
int n,m,a[N],c[N];
int lowbit(int x)
{
return x&-x;
}
void add(int x,int y)
{
for(int i=x;i<=n;i+=lowbit(i)) c[i]+=y;
}
int sum(int x)
{
int ans=0;
for(int i=x;i;i-=lowbit(i)) ans+=c[i];
return ans;
}
int main ()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>a[i];
add(i,a[i]);
}
while(m--)
{
int op,x,y;
cin>>op>>x>>y;
if(op==1) add(x,y);
else cout<<sum(y)-sum(x-1)<<"\n";
}
return 0;
}
例2 P3368 【模板】树状数组 2
https://www.luogu.com.cn/problem/P3368
分析
区间修改和单点查询,就像用普通数组一样思考,显然,差分数组就能轻易做到区间修改。用树状数组维护差分数组,就能轻易做到区间修改和单点查询。
- 区间修改:例如将
的值加上 ,只需要将 加上 , 减去 。 - 单点查询:查询
就是查 的和,直接求就好了
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5e5+5;
int n,m,a[N],c[N];
int lowbit(int x)
{
return x&-x;
}
void add(int x,int y)
{
for(int i=x;i<=n;i+=lowbit(i)) c[i]+=y;
}
int sum(int x)
{
int ans=0;
for(int i=x;i;i-=lowbit(i)) ans+=c[i];
return ans;
}
int main ()
{
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i],add(i,a[i]-a[i-1]);
while(m--)
{
int op,x,y,k;
cin>>op;
if(op==1)
{
cin>>x>>y>>k;
add(x,k);
add(y+1,-k);
}
else
{
cin>>x;
cout<<sum(x)<<"\n";
}
}
return 0;
}
例3
题目描述
既然已经知道了如何单点修改区间查询以及区间修改和单点查询,那么能不能做到区间修改区间查询呢?
分析
首先肯定是可以的,且复杂度能比数组更优,只需要稍微使用一点点技巧。还是维护差分数组,区间修改还是那样,主要思考区间查询。
注意看
放到图中来看:
黑色数据相加就是所需,将其补全,整个表就是
也就是说我们要维护两个前缀和,
code
code 不保证都正确。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+5;
int n,m,a[N],c1[N],c2[N];
int lowbit(int x)
{
return x&-x;
}
void add(int c[],int x,int y)
{
for(int i=x;i<=n;i+=lowbit(i)) c[i]+=y;
}
int sum(int c[],int x)
{
int ans=0;
for(int i=x;i;i-=lowbit(i)) ans+=c[i];
return ans;
}
int query(int x)
{
return sum(c1,x)*(x+1)-sum(c2,x);
}
int main ()
{
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++)
{
int b=a[i]-a[i-1];
add(c1,i,b);
add(c2,i,b*i);
}
while(m--)
{
int op,l,r,x;
cin>>op>>l>>r;
if(op==1)
{
cin>>x;
add(c1,l,x),add(c1,r+1,-x);
add(c2,l,l*x),add(c2,r+1,(r+1)*(-x));
}
else cout<<query(r)-query(l-1)<<"\n";
}
return 0;
}
例4 P1908 逆序对
https://www.luogu.com.cn/problem/P1908
分析
首先数据跨度大,可以离散化,离散化后用树状数组维护权值数组。以离散化后的值为下标,存贮出现的数量。
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5e5+5;
int n,m,a[N],b[N],c[N];
int lowbit(int x)
{
return x&-x;
}
void add(int x,int y)
{
for(int i=x;i<=n;i+=lowbit(i)) c[i]+=y;
}
int sum(int x)
{
int ans=0;
for(int i=x;i;i-=lowbit(i)) ans+=c[i];
return ans;
}
int main ()
{
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i],b[i]=a[i];
sort(b+1,b+n+1);
m=unique(b+1,b+n+1)-b-1;
ll ans=0;
for(int i=n;i;i--)
{
int k=lower_bound(b+1,b+m+1,a[i])-b;
ans+=sum(k-1);
add(k,1);
}
cout<<ans<<"\n";
return 0;
}
练习
tips
- 注意前缀和的范围,要不要开
long long
。 - 树状数组维护的是前缀和数组,差分数组还是权值数组。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」