树状数组笔记

树状数组是算法竞赛中常见的高级数据结构,是利用数的二进制特征进行检索的树状数据结构。常用于求解动态变化的区间问题。具有结构清晰,操作方便,应用灵活,效率高的特点。

单点修改+区间查询

这是树状数组最基础的应用,设 BIT[i] 表示 [ilowbit(i)+1,i] 区间内的数字和。此处 sum 的作用是求区间 [1,x] 值的和。

void LSB(int x){return x&(-x);}
void add(int x,int d){
while(x<=n){BIT[x]+=d;x+=LSB(x);}
}
int query(int x){
int ans=0;
while(x){ans+=BIT[x];x-=LSB(x);}
return ans;
}

区间修改+单点查询

可以利用差分数组将区间修改变为单点修改。设 d[i]a[i] 的差分数组,修改区间 [L,R] 时,将 d[L] 增加,d[R+1] 减少,求解 a[x] 即求 i=1xd[i]。可以利用树状数组维护。

void LSB(int x){return x&(-x);}
void add(int x,int d){
while(x<=n){BIT[x]+=d;x+=LSB(x);}
}
void change(int l,int r,int d){add(l,d);add(r+1,-d);}
int query(int x){
int ans=0;
while(x){ans+=BIT[x];x-=LSB(x);}
return ans;
}

区间修改+区间查询

这里需要两个树状数组才能高效完成 “区间修改+区间查询”,称为 “二阶树状数组”。

定义 d[i]a[i] 的差分数组,区间修改的方法上面已经讲过,下面推导区间和:

i=1kak=d1+(d1+d2)+...+i=1kdk

=k·d1+(k1)·d2+...+dk

=ki=1kdii=1k(i1)di

因此我们可以维护两个树状数组,一个计算 di 的前缀和,一个计算 (i1)·di 的前缀和。在 O(logn) 的时间内进行 “区间修改+区间查询”。

void add(int x,int d1,int d2){
while(x<=n){
BIT1[x]+=d1; BIT2[x]+=d2;
x+=LSB(x);
}
}
int query1(int x){
int ans=0;
while(x){ans+=BIT1[x];x-=LSB(x);}
return ans;
}
int query2(int x){
int ans=0;
while(x){ans+=BIT2[x];x-=LSB(x);}
return ans;
}
void update(int l,int r,int d){
add(l,d,d*(l-1));
add2(r+1,-d,-d*r);
}
void query(int l,int r){
return r*query1(r)-query2(r)-(l-1)*query(l-1)+query2(l-1);
}

二维 “区间修改+区间查询”

二维 “区间修改+区间查询” 是一维的拓展,推导方法和一维的类似。

d[i][j]a[i][j] 的差分,有:

d[i][j]=a[i][j]a[i1][j]a[i][j1]+a[i1][j1]

此时 a[i][j]=k=1il=1jd[k][l]

接下来推导区间和,以顶点为 (a,b)(c,d) 的矩阵的区间和,有:

i=acj=bda[i][j]=i=1cj=1da[i][j]i=1cj=1b1a[i][j]+i=1a1j=1da[i][j]+i=1a1j=1b1a[i][j]

可以看出问题的关键在于求解 i=1nj=1ma[i][j] 的值。

i=1nj=1ma[i][j]

=i=1nj=1mk=1il=1jd[k][l]

=i=1nj=1md[i][j]·(ni+1)·(mj+1)

=(n+1)(m+1)i=1nj=1md[i][j](m+1)i=1nj=1md[i][j]·i(m+1)i=1nj=1md[i][j]·j+i=1nj=1md[i][j]·ij

此时需要维护四个树状数组,分别计算 d[i][j],d[i][j]·i,d[i][j]·j,d[i][j]·ij

void add(int x,int y,int d){
for(int i=x;i<=n;i+=LSB(i)){
for(int j=y;j<=m;j+=LSB(j)){
t1[i][j]+=d; t2[i][j]+=x*d;
t3[i][j]+=y*d; t4[i][j]+=x*y*d;
}
}
}
void update(int a,int b,int c,int d,int delta){
add(a,b,delta); add(c+1,d+1,delta);
add(a,d+1,-delta); add(c+1,b,-delta);
}
int sum(int x,int y){
int ans=0;
for(int i=x;i;i-=LSB(i)){
for(int j=y;j;j-=LSB(j)){
ans+=(x+1)*(y+1)*t1[i][j]-(y+1)*t2[i][j]-(x+1)*t3[i][j]+t4[i][j];
}
}
return ans;
}
void query(int a,int b,int c,int d){
return sum(c,d)+sum(a-1,b-1)-sum(c,b-1)-sum(a-1,d);
}

逆序对

逆序对指序列 ai<jai>aj 的二元组 (i,j) 的数量。

我们把数字看作下标,每统计一个数字 ai,就将下标对应的元素加 1,然后计算 [1,ai1] 的前缀和,就是逆序对的数量。

如果值域太大,可以使用离散化。

posted @   11jiang08  阅读(13)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示