核心思想
每个位置的覆盖范围由二进制最低位的
1
1
1决定。
例如:
索引
6
6
6(二进制为
110
110
110) 管理
2
2
2个元素(
5
5
5到
6
6
6)
索引
8
8
8(二进制为
1000
1000
1000) 管理
8
8
8个元素(
1
1
1-
8
8
8)
那这个覆盖范围就是最低位的
1
1
1来决定,说人话就是这个数的二进制从右往左数第一个
1
1
1为最高位,找过的
0
0
0一次排在次低位的数。对
x
x
x找这样的数称为
l
o
w
b
i
t
(
x
)
lowbit(x)
lowbit(x)。
比如说
6
(
11
0
2
)
6(110_2)
6(1102),他的
l
o
w
b
i
t
lowbit
lowbit值就是
2
(
1
0
2
)
2(10_2)
2(102)
而
8
(
100
0
2
)
8(1000_2)
8(10002),整个二进制就开头一个
1
1
1,所以
l
o
w
b
i
t
(
8
)
lowbit(8)
lowbit(8)就是他自己。
而这个
l
o
w
i
b
t
lowibt
lowibt值的求法就是按位与自己的相反数
int lowbit(int x){
return x&-x;
}
然后树状数组的核心代码就完了。
更新操作
单点修改
比如说要对点 x x x加上 k k k,那么和线段树的思想差不多,当子节点被改动时,上层节点的值也会变动,怎么找到上层节点呢? x x x的上层节点就是 l o w b i t ( x ) lowbit(x) lowbit(x),所以改完点 x x x的值之后,就应该改动点 l o w b i t ( x ) lowbit(x) lowbit(x)的值,使其一直维护区间和。一直往上找,直到找到了一个点包含整个数组的区间和(根节点)为止。
void change(int x,int k){
while(x<=n){
f[x]+=k;
x+=lowbit(x);
}
}
区间查询
树状数组的区间查询类似前缀和,是
如果我们要查询
a
1
a_1
a1 ~
a
x
a_x
ax的和,先看一下核心思想部分,那个部分讲过点
x
x
x的覆盖范围是
l
o
w
b
i
t
(
x
)
lowbit(x)
lowbit(x),也就是说在大小为
l
o
w
b
i
t
(
x
)
lowbit(x)
lowbit(x)的范围内,这个区间的点之和是被点
x
x
x包含的,所以这个区间的元素和就存在点
x
x
x之中,不用再计算,那么我们就要减去点
x
x
x所覆盖的区间,查找下一个区间,所以就在统计完之后用
x
x
x减去
l
o
w
b
i
t
(
x
)
lowbit(x)
lowbit(x)。直到剪完了,
1
1
1 ~
x
x
x都查完了为止。
int query(int x){
int res=0;
while(x){
res+=f[x];
x-=lowbit(x);
}
return res;
}
然后如果要查找区间
l
l
l ~
r
r
r,就想前缀和那样,用
q
u
e
r
y
(
r
)
query(r)
query(r)减去
q
u
e
r
y
(
l
−
1
)
query(l-1)
query(l−1)就行了。
整个树状数组的主要函数就讲完了。
模版题:P3374【模板】树状数组 1
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5;
int a[N];
int bit[N];
int n,m;
int lowbit(int x){
return x&-x;
}
void change(int x,int k){
while(x<=n){
bit[x]+=k;
x+=lowbit(x);
}
}
int query(int x){
int res=0;
while(x){
res+=bit[x];
x-=lowbit(x);
}
return res;
}
signed main(){
ios::sync_with_stdio(0);
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
change(i,a[i]);//将原数组存入树状数组
}
while(m--){
int op,l,r;
cin>>op>>l>>r;
if(op==1){
change(l,r);
}
else{
cout<<query(r)-query(l-1)<<'\n';
}
}
}
例题2
接下来再来看另一道题:P3368 【模板】树状数组 2
区间修改,阁下又该如何应对
思路
但是这道题是点查询,所以我们不用写真正的区间修改。
我们只需要用树状数组维护修改的内容就行了。
change(l,k);
change(r+1,-k);
至于点查询,就是用 q u e r y ( x ) query(x) query(x)减去 q u e r y ( x − 1 ) query(x-1) query(x−1)就行了。
代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int a[N];
int f[N];
int n,m;
int lowbit(int x){
return x&-x;
}
void change(int x,int k){
while(x<=n){
f[x]+=k;
x+=lowbit(x);
}
}
int query(int x){
int res=0;
while(x){
res+=f[x];
x-=lowbit(x);
}
return res;
}
signed main(){
ios::sync_with_stdio(0);
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
}
while(m--){
int op,l,r,k;
cin>>op>>l;
if(op==1){
cin>>r>>k;
change(l,k);
change(r+1,-k);
}
else{
cout<<a[l]+query(l)<<'\n';
}
}
}