本文以P3374为例讲解树状数组+单点修改/查询
其他树状数组相关文章:
P3374 【模板】树状数组 1
题目描述
如题,已知一个数列,你需要进行下面两种操作:
-
将某一个数加上 x x x
-
求出某区间每一个数的和
输入格式
第一行包含两个正整数 n , m n,m n,m,分别表示该数列数字的个数和操作的总个数。
第二行包含 n n n 个用空格分隔的整数,其中第 i i i 个数字表示数列第 i i i 项的初始值。
接下来 m m m 行每行包含 3 3 3 个整数,表示一个操作,具体如下:
-
1 x k
含义:将第 x x x 个数加上 k k k -
2 x y
含义:输出区间 [ x , y ] [x,y] [x,y] 内每个数的和
输出格式
输出包含若干行整数,即为所有操作 2 2 2 的结果。
样例 #1
样例输入 #1
5 5
1 5 4 2 3
1 1 3
2 2 5
1 3 -1
1 4 2
2 1 4
样例输出 #1
14
16
提示
【数据范围】
对于
30
%
30\%
30% 的数据,
1
≤
n
≤
8
1 \le n \le 8
1≤n≤8,
1
≤
m
≤
10
1\le m \le 10
1≤m≤10;
对于
70
%
70\%
70% 的数据,
1
≤
n
,
m
≤
1
0
4
1\le n,m \le 10^4
1≤n,m≤104;
对于
100
%
100\%
100% 的数据,
1
≤
n
,
m
≤
5
×
1
0
5
1\le n,m \le 5\times 10^5
1≤n,m≤5×105。
数据保证对于任意时刻, a a a 的任意子区间(包括长度为 1 1 1 和 n n n 的子区间)和均在 [ − 2 31 , 2 31 ) [-2^{31}, 2^{31}) [−231,231) 范围内。
样例说明:
故输出结果14、16
树状数组讲解
1.树状数组是什么:
是一种用来维护n个数前缀和信息的数据结构
2.树状数组怎么建立与使用:
(1)lowbit()函数:
①lowbit()函数是什么:
lowbit()函数用来取一个二进制最低位的1与后边的0组成的数,
比如 2 ( 10 ) = 1 0 ( 2 ) , 2_{(10)}=10_{(2)}, 2(10)=10(2),则 l o w b i t ( 2 ) = 1 0 ( 2 ) = 2 ( 10 ) lowbit(2)=10_{(2)}=2_{(10)} lowbit(2)=10(2)=2(10),
而 6 ( 10 ) = 11 0 ( 2 ) , 6_{(10)}=110_{(2)}, 6(10)=110(2),最低位的1是从左往右第2个,则 l o w b i t ( 6 ) = 1 0 ( 2 ) = 2 ( 10 ) lowbit(6)=10_{(2)}=2_{(10)} lowbit(6)=10(2)=2(10),
再如因为 2 0 ( 10 ) = 1010 0 ( 2 ) 20_{(10)}=10100_{(2)} 20(10)=10100(2)所以 l o w b i t ( 20 ) = 10 0 ( 2 ) = 4 ( 10 ) lowbit(20)=100_{(2)}=4_{(10)} lowbit(20)=100(2)=4(10)
②lowbit()怎么实现:
我们经过学习知道,在计算机中,负数以其正值的补码形式表达:一个整数,按照绝对值大小转换成的二进制数,称为原码;将二进制数按位取反,所得的原二进制数的反码;反码加1得到补码
设原码是 a 1 a 2 a 3 … a n a_1a_2a_3…a_n a1a2a3…an( a i a_i ai均为0或1)先取反码得 ! a 1 ! a 2 ! a 3 … ! a n !a_1!a_2!a_3…!a_n !a1!a2!a3…!an( ! ! !为取反1->0,0->1)。
再加1,则会一直进位(即取反)至反码最右的0(原码中为1),那么会变成 ! a 1 ! a 2 ! a 3 … a x … a n !a_1!a_2!a_3…a_x…a_n !a1!a2!a3…ax…an(设 ! a x = 0 !a_x=0 !ax=0,则 ! a x + 1 !a_{x+1} !ax+1~ ! a n 均为 1 !a_n均为1 !an均为1),相当于把原码最右的1的左边都取反,其他数位不变。
我们再把x&(-x)(&是按位与)发现原码最右的1的左边都取反了不相等都得0,原码最右的1再补码中也是1得1,原码最右的1右边都同为0得0,显然得到的就是 l o w b i t ( x ) lowbit(x) lowbit(x)
至此推得 l o w b i t ( x ) = x lowbit(x)=x lowbit(x)=x& ( − x ) (-x) (−x)
③lowbit()与树状数组的关联
已知 l o w b i t ( 1 ) = 1 , l o w b i t ( 2 ) = 2 , l o w b i t ( 3 ) = 1 , l o w b i t ( 4 ) = 4 , l o w b i t ( 5 ) = 1 , l o w b i t ( 6 ) = 4 , l o w b i t ( 7 ) = 1 lowbit(1)=1,lowbit(2)=2,lowbit(3)=1,lowbit(4)=4,lowbit(5)=1,lowbit(6)=4,lowbit(7)=1 lowbit(1)=1,lowbit(2)=2,lowbit(3)=1,lowbit(4)=4,lowbit(5)=1,lowbit(6)=4,lowbit(7)=1
如图,连线表示父节点值是子节点各值和,点的编号就是在我们的树状数组相应的下标,树状数组上的每一点都有相应的区间,存的值就是这段区间的和
通过观察,树状数组t[i]一定包含了原数组a[i]的值。
此外我们可以发现一个节点的父节点编号( f i d fid fid)与当前节点编号( s i d sid sid)存在关系fid=sid+lowbit(sid);
还有与某点(编号
i
d
id
id) 范围(
a
[
i
]
a[i]
a[i]~
a
[
i
+
x
]
a[i+x]
a[i+x]) 相接的范围(
a
[
j
]
a[j]
a[j]~
a
[
i
−
1
]
a[i-1]
a[i−1])的点的编号(
b
i
d
bid
bid)有关系bid=id-lowbit(id) 至于证明我不会
而且树状数组大小与原数组大小相等
(2)用lowbit()函数建造、使用树状数组
①建树
当我们得知a[i]时,先将t[i]+=a[i],因为父节点值是覆盖子节点的,子变使父变,所以t[i]的父亲t[i+lowbit(i)]+=a[i] 也是必须的。如此循环把上一辈的值修改。但是祖祖辈辈无穷匮也,所以需要限定条件i<=n,我们不需要更早的祖先了。
因此需要for循环for(;i<=n;i+=lowbit(i)) t[i]+=a[i]
②维护
之所以会有树状数组就是因为原版的前缀和难以维护,一值修改后面的前缀和全要重新算。但树状数组不一样,修改量可以缩很多(详见下)。如a[i]增大x,就是要给树状数组上 所有覆盖了这个点的 区间的 点值增加x,具体和建树差不多循环for(;i<=n;i+=lowbit(i)) t[i]+=x
就行了
③使用
我们辛辛苦苦写出来树状数组,那咋用呢???
它的本质其实是前缀和。比如我要求a[0]~a[i]的所有数的和,用树状数组就是一个for循环:for(;i!=0;i-=lowbit(i)) ans+=t[i]
。(一直加前面有相邻区间的点的值直至加完)
于是就可以求任意区间的和了。
(3)完整代码:
#include<bits/stdc++.h>
using namespace std;
int n,m;
int a[500005];
int t[500005];
int lowbit(int x){
return x&(-x);
}
int sum(int x){
int ans=0;
for(;x;x-=lowbit(x)){
ans+=t[x];
}
return ans;
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
for(int j=i;j<=n;j=j+lowbit(j)){
t[j]+=a[i];
}
}
while(m--){
int work,x,y,k;
cin>>work;
if(work==1){
cin>>x>>k;
for(;x<=n;x=x+lowbit(x)){
t[x]+=k;
}
}
else{
cin>>x>>y;
cout<<sum(y)-sum(x-1)<<endl;
}
}
return 0;
}
3.树状数组其他用法:
以上是为单点修改+区间查询,实际上还可以实现区间修改+单点查询(文章链接见上)、区间修改+区间查询(文章链接见上)
4.树状数组与其它数据结构时间复杂度对照:
操作\数据结构 | 数组 | 前缀和 | 线段树 | 树状数组 |
---|---|---|---|---|
单点修改 | O ( 1 ) O(1) O(1) | O ( n ) O(n) O(n) | O ( l o g n ) O(logn) O(logn) | O ( l o g n ) O(logn) O(logn) |
区间修改和查询 | O ( n ) O(n) O(n) | O ( 1 ) O(1) O(1) | O ( l o g n ) O(logn) O(logn) | O ( l o g n ) O(logn) O(logn) |
写了一个晚上,能给个赞吗?QWQ
如有错误,欢迎在评论区指正!