专栏文章
树状数组
算法·理论参与者 3已保存评论 2
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 2 条
- 当前快照
- 1 份
- 快照标识符
- @mlmknykc
- 此快照首次捕获于
- 2026/02/15 01:10 5 天前
- 此快照最后确认于
- 2026/02/19 01:12 14 小时前
原理
先看下面这图:
填上数值:

填上数值:

这样我们可以求任意区间的区间和
例:
但这样会有冗余,
于是我们把每一行的偶数位删除:
于是删除的位数都可以用减法得到
于是删除的位数都可以用减法得到然后让剩下的每一个区间对应上一个数组的值:


我们发现:
对应的长度是 转二进制后的最末尾的 1 和其后面的 0 组成的数值, 记作
例:
:它的二进制是,所以也就对应着它的区间长度
lowbit的实现
将的二进制全部取反记为,再将 & ,就可以得到最后一位 的位置
详细请看: https://www.cnblogs.com/fusiwei/p/11752540.html
code:
CPPint lowbit(int x) {
return (x & (-x));
}
区间查询
根据前缀和的定义,每一个区间和都可以由得来
这样求一个区间就被拆分成两个小过程
然后我们看一些例子(根据图四):
- 可以分为:
- 可以分为:
看1号例子: 的 是由后面的 的 得来的, 的 是由后面的 的 得来的
结论:
所以,我们就可以从当前不停往前加,求出的值
再用前缀和求出当前区间的值
code:
CPPint query(int x){
int ans=0;
while(x>0){
ans+=c[x];
x-=lowbit(x);
}
return ans;
}
query(r)-query(l-1)//[l:r]的区间值
单点修改
因为每一个区间都是由一个它包含的子区间的和得来
若我们修改一个点的值,那么它所对应的区间的和要改,包含这个区间的区间和也要修改,包含包含这个区间的区间的区间和也要修改(禁止套娃)
例子(根据图四):
修改号点的值,那么也需修改,也就是:、、
观察一下: 的 是由 得来, 的 是由 得来
结论:
但: 值最大取到 ,所以循环边界就是
code:
CPPvoid modify(int x,int v){
while(x<=n){
c[x]+=v;
x+=lowbit(x);
}
}
区间修改
这里需用到差分
差分
详细请见: https://oi-wiki.org/basic/prefix-sum/
是前缀和的逆运算,先需求差分数组()
修改区间,就是让
,
再求一遍前缀和得到修改过后的原数组
但这样修改虽是的,但查询却是的,是一种适用于大量修改、少量查询的算法
我们让树状数组维护这个差分的过程,每次让加上,让减去,于是我们就记录下了区间的值
code:
CPPvoid update(int x,int v){
while(x<=n){
c[x]+=v;
x+=lowbit(x);
}
}
update(l,x);
update(r+1,-x);
单点查询
根据区间修改,我们得知了每个区间的被修改的值
于是我们只需求出包含当前点的区间总和+当前点原来的数值
code:
CPPint query(int x){
int ans=0;
while(x>0){
ans+=c[x];
x-=lowbit(x);
}
return ans;
}
a[x]+query(x)
例题
T1.U438237 ١١(❛ᴗ❛)【树状数组+线段树】-模板(单点修改+区间查询)
这一题模板题,直接套不多讲
code:
CPP#include<bits/stdc++.h>
using namespace std;
#define int long long
int c[1000010];
int n,q;
int lowbit(int x){
return (x&(-x));
}
int query(int x){
int ans=0;
while(x>0){
ans+=c[x];
x-=lowbit(x);
}
return ans;
}
void update(int x,int v){
while(x<=n){
c[x]+=v;
x+=lowbit(x);
}
}
signed main(){
cin>>n>>q;
for(int v,i=1;i<=n;i++){
cin>>v;
update(i,v);
}
for(int i=1;i<=q;i++){
int op,l,r;
cin>>op>>l>>r;
if(op==1){
update(l,r);//单点修改
}else{
cout<<query(r)-query(l-1)<<endl;//区间查询求前缀和
}
}
return 0;
}
T2.U438249 清点人数
这一题与上题板子的区别有:
- 这一题区间查询只需求的区间和
- 有加有减
code:
CPP#include<bits/stdc++.h>
using namespace std;
#define int long long
int c[1000010];
int n,k;
int lowbit(int x){
return (x&(-x));
}
int query(int x){
int ans=0;
while(x>0){
ans+=c[x];
x-=lowbit(x);
}
return ans;
}
void update(int x,int v){
while(x<=n){
c[x]+=v;
x+=lowbit(x);
}
}
signed main(){
cin>>n>>k;
for(int i=1;i<=k;i++){
char op;
cin>>op;
if(op=='A'){
int m;
cin>>m;
cout<<query(m)<<endl;//只需求[1,m]的区间和
}else if(op=='B'){
int m,p;
cin>>m>>p;
update(m,p);//加就是正数
}else{
int m,p;
cin>>m>>p;
update(m,-p);//减就是负数
}
}
return 0;
}
T3.P3368 【模板】树状数组 2
此题是区间修改+单点查询的模板,讲解上面有不多讲
code:
CPP#include<bits/stdc++.h>
using namespace std;
int c[500010],a[500010];
int n,m;
int lowbit(int x){
return (x&(-x));
}
int query(int x){
int ans=0;
while(x>0){
ans+=c[x];
x-=lowbit(x);
}
return ans;
}
void update(int x,int v){
while(x<=n){
c[x]+=v;
x+=lowbit(x);
}
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=m;i++){
int op;
cin>>op;
if(op==1){
int l,r,x;
cin>>l>>r>>x;
update(l,x);
update(r+1,-x);
}else{
int x;
cin>>x;
cout<<a[x]+query(x)<<"\n";
}
}
return 0;
}
T4.P5057 [CQOI2006] 简单题
这道题也是区间修改+单点查询的题
与上题的区别:
- 这里是异或
所以每次查询时只需看包含这个点的区间的状态求它们异或和
每次修改也是跟上一样,因为一个二进制数异或上两个一还是它本身没有变
code:
CPP#include<bits/stdc++.h>
using namespace std;
#define int long long
int c[1000010];
int a[1000010];
int n,m;
int lowbit(int x){
return (x&(-x));
}
int query(int x){
int ans=0;
while(x>0){
ans^=c[x];
x-=lowbit(x);
}
return ans;
}
void update(int x,int v){
while(x<=n){
c[x]^=v;
x+=lowbit(x);
}
}
signed main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
int op;
cin>>op;
if(op==1){
int l,r;
cin>>l>>r;
update(l,1);
update(r+1,1);
}else{
int x;
cin>>x;
cout<<query(x)<<endl;
}
}
return 0;
}
离散化
因下几题需离散化,所以来讲一下
离散化,本质上是一种哈希,它在哈希后依然能保证顺序不变
它适用于数字很大,但只看相对大小的问题
于是我们可以按顺序给它们编号
离散化分两种:
- 相同的值有不同的编号(一般按出现顺序编号)
- 相同的值是相同的编号
例:
| 1 | 3 | 4 | 2 | 6 | 2 |
|---|
我们先排序
| 1 | 2 | 2 | 3 | 4 | 6 |
|---|
再编号
第1种:
| 1 | 2 | 2 | 3 | 4 | 6 |
|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 |
第2种:
| 1 | 2 | 2 | 3 | 4 | 6 |
|---|---|---|---|---|---|
| 1 | 2 | 2 | 3 | 4 | 5 |
所以用代码模拟以上过程即可
T1.U523499 ١١(❛ᴗ❛)【离散化】-并列大小
这道题属于第2种离散化
每一个相同的值都要编号成相同的号码
做法:
- 先复制一份原数组
- 将复制数组从小到大排序
- 将复制数组去重
- 二分查找原数组再复制数组中的位置也就是编号,它就是离散化后的值
code:
CPP#include<bits/stdc++.h>
using namespace std;
int a[100010];
int tmp[100010];
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
tmp[i]=a[i];//复制数组
}
sort(tmp+1,tmp+1+n);//排序
int cnt=unique(tmp+1,tmp+1+n)-tmp-1;//去重
for(int i=1;i<=n;i++){
a[i]=lower_bound(tmp+1,tmp+1+cnt,a[i])-tmp;//查找位置
}
for(int i=1;i<=n;i++){
cout<<a[i]<<" ";
}
return 0;
}
T2.U523492 ١١(❛ᴗ❛)【离散化】-非并列排名
这道题属于第1种离散化
每一个相同的值都要编号成不同的号码
做法:
- 拿一个结构体装它的数值和位置
- 排序,若值不同按值从大到小排序,若值相同按位置从小到大排序
- 将离散化后的数字放回原数组
code:
CPP#include<bits/stdc++.h>
using namespace std;
struct node{
int v,id;
};
node a[100010];
int pos[100010];
bool cmp(node x,node y){
if(x.v!=y.v)return x.v>y.v;//值不同按值从大到小排序
return x.id<y.id;值相同按位置从小到大排序
}
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i].v;//记录值
a[i].id=i;//记录位置
}
sort(a+1,a+1+n,cmp);
for(int i=1;i<=n;i++){
pos[a[i].id]=i;//将离散化的数装回去
}
for(int i=1;i<=n;i++){
cout<<pos[i]<<" ";
}
return 0;
}
值域树状数组
T5.U438285 ١١(❛ᴗ❛)【树状数组】-数星星 Stars
先看输入,此题是按坐标增序给出, 坐标相同的按坐标增序给出
所以只要在当前点的左边且这样它就可以升上一级
我们让大的数为一个区间,它下面包含许多小于等于它的数
所以当小于等于它的数来了一个后,它的数值也会+1
code:
CPP#include<bits/stdc++.h>
using namespace std;
int c[32010];
int ans[32010];
struct node{
int x,y;
}a[32010];
int n;
int lowbit(int x){
return (x&(-x));
}
int query(int x){
int sum=0;
while(x>0){
sum+=c[x];
x-=lowbit(x);
}
return sum;
}
void update(int x,int v){
while(x<=32001){//不是枚举到n
c[x]+=v;
x+=lowbit(x);
}
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i].x>>a[i].y;
a[i].x++;//避免从0开始
}
for(int i=1;i<=n;i++){
ans[query(a[i].x)]++;//查询当前星星的等级并计数
update(a[i].x,1);//将当前星星加入树状数组
}
for(int i=0;i<n;i++){
cout<<ans[i]<<endl;
}
return 0;
}
T6.P1908 逆序对
这道题我们注意到,每个数字的值很大且只要比较它们的相对大小,所以要使用离散化
然后这道题看逆序对也就是找且的点
所以用值域树状数组来维护.
code:
CPP#include<bits/stdc++.h>
using namespace std;
int c[500010],a[500010],tmp[500010];
int n,cnt;
int lowbit(int x){
return (x&(-x));
}
int query(int x){
int ans=0;
while(x>0){
ans+=c[x];
x-=lowbit(x);
}
return ans;
}
void update(int x,int v){
while(x<500010){
c[x]+=v;
x+=lowbit(x);
}
}
void lsh(){
sort(tmp+1,tmp+1+n);
int cnt=unique(tmp+1,tmp+1+n)-tmp-1;
for(int i=1;i<=n;i++){
a[i]=lower_bound(tmp+1,tmp+1+cnt,a[i])-tmp;
a[i]=cnt-a[i]+1;
}
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
tmp[i]=a[i];
}
lsh();
long long ans=0;
for(int i=1;i<=n;i++){
ans+=query(a[i]-1);
update(a[i],1);
}
cout<<ans;
return 0;
}
相关推荐
评论
共 2 条评论,欢迎与作者交流。
正在加载评论...