引入
什么是树状数组?它能做什么?
树状数组是一种支持单点修改和区间求和的数据结构
当然,这并不代表它的作用仅限于此,它同样可以实现区间修改
基础树状数组
基础树状数组能够用 O ( n log ( n ) ) O(n\log(n)) O(nlog(n))的时间实现单点修改和区间求和
树状数组的存储
当我们想要求一个数列的和时,我们就直接累加(毕竟暴力出奇迹 ),但这样是不是慢了一点……
事实上,树状数组就可以解决这个问题。它就有一点倍增的思想在内了。
首先,我们先来了解一下
l
o
w
b
i
t
lowbit
lowbit 函数,树状数组就与它有关。它是指一个数转化为二进制后最小位的“1”所代表的值
举个栗子,
l
o
w
b
i
t
(
1
)
=
1
lowbit(1)=1
lowbit(1)=1 ,
l
o
w
b
i
t
(
6
)
=
2
lowbit(6)=2
lowbit(6)=2
理解了?
要不我们现在开始求
l
o
w
b
i
t
(
i
)
lowbit(i)
lowbit(i)吧,把
i
i
i化为二进制固然可以,但代码太长了(是的没错,四五行确实太长了 )
其实通过补码我们就能求出
l
o
w
b
i
t
(
i
)
lowbit(i)
lowbit(i)。
奉上代码:
int lowbit(int x){
return x&(-x)
}
好,我们继续来看,如果有了
l
o
w
b
i
t
lowbit
lowbit ,数字可以怎么拆分?
嗯,要不先从“6”开始吧。“6”按照二进制可拆分为“110”,没错吧。
我们按照从低位到高位来拆分,那么6可以先拆出一个2,即
l
o
w
b
i
t
(
6
)
lowbit(6)
lowbit(6) 。剩下4,而
l
o
w
b
i
t
(
4
)
lowbit(4)
lowbit(4) 正好等于4,拆完了。
其实树状数组的存储和使用我们到这里都讲完了,如果你注意思考的话。
我们可以让
s
[
i
]
s[i]
s[i] 存储以
i
i
i 为尾,连续
l
o
w
b
i
t
(
i
)
lowbit(i)
lowbit(i) 个数的和。这样存储的部分就结束了。时间复杂度分析 对于每个
s
[
i
]
s[i]
s[i],都会加
x
≤
log
(
i
)
x \le \log(i)
x≤log(i) 次,所以时间复杂度为
n
log
(
n
)
n\log(n)
nlog(n)
树状数组的修改
修改就按照上面的树就行啦
比如说,如果
a
[
5
]
a[5]
a[5] 改变了,那么
s
[
5
]
,
s
[
6
]
,
s
[
8
]
s[5],s[6],s[8]
s[5],s[6],s[8]就要改变,我们发现它实际上可以稍微移动,就是完全二叉树。每次修改都是
l
o
g
(
n
)
log(n)
log(n)的啦。
void add(int x,int v){
while(x<=n){
c[x]+=v;
x+=lowbit(x);
}
}
树状数组的求和
在上面的树中,我们就能发现,
a
[
1
]
+
a
[
2
]
+
a
[
3
]
+
a
[
4
]
+
a
[
5
]
+
a
[
6
]
+
a
[
7
]
a[1]+a[2]+a[3]+a[4]+a[5]+a[6]+a[7]
a[1]+a[2]+a[3]+a[4]+a[5]+a[6]+a[7]可以拆分为:
a
[
1
]
+
a
[
2
]
+
a
[
3
]
+
a
[
4
]
a[1]+a[2]+a[3]+a[4]
a[1]+a[2]+a[3]+a[4],
a
[
5
]
+
a
[
6
]
a[5]+a[6]
a[5]+a[6],
a
[
7
]
a[7]
a[7]
这就和前面的拆分方法有关了
代码如下:
int sum(int x){
int ret=0;
while(x>0){
ret+=c[x];
x-=lowbit(x);
}
return ret;
}
所以
a
[
l
]
+
a
[
l
+
1
]
+
.
.
.
.
.
.
+
a
[
r
]
a[l]+a[l+1]+......+a[r]
a[l]+a[l+1]+......+a[r]怎么求呢,当然是
s
u
m
(
r
)
−
s
u
m
(
l
−
1
)
sum(r)-sum(l-1)
sum(r)−sum(l−1)了
到这里,基础的树状数组部分正式结束,要不看几道题吧
题目
洛谷P3374【模版】树状数组1
P3374题目通道
这真的是模板题了,没什么好说的,把前面的总结一下就能做出来了
AC代码:
#include<iostream>
using namespace std;
int n,m,c[1000005];
int lowbit(int x){
return x&-x;
}
void add(int x,int v){
while(x<=n){
c[x]+=v;
x+=lowbit(x);
}
}
int sum(int x){
int ret=0;
while(x>0){
ret+=c[x];
x-=lowbit(x);
}
return ret;
}
int main(){
cin>>n>>m;
for(int i=1,x;i<=n;i++){
cin>>x;
add(i,x);
}
for(int i=1,k,l,r;i<=m;i++){
cin>>k>>l>>r;
if(k==2){
cout<<sum(r)-sum(l-1)<<endl;
}else{
add(l,r);
}
}
return 0;
}
先把模板给出来,题目的话后面我再补充
树状数组+差分
光是树状数组只能实现单点修改,但是如果你会前缀和和差分的话,就可以实现区间修改
本着循序渐进的原则,我们先来讨论较简单的区间修改,单点查询
单点查询
我们可以按照差分的方式来存储原数列,这个确实挺简单,如果你会差分(不会就任由天命吧 )
假设我们用差分存储的数列叫
b
[
i
]
b[i]
b[i],那么按照差分的性质,
b
[
i
]
=
a
[
i
]
−
a
[
i
−
1
]
b[i]=a[i]-a[i-1]
b[i]=a[i]−a[i−1]。注意:我们默认
a
[
0
]
=
0
a[0]=0
a[0]=0,尽管并没有输入。
那么如果要对
[
l
,
r
]
[l,r]
[l,r]内的
a
[
i
]
a[i]
a[i]进行修改(比如说加
x
x
x),怎么办
差分的思想告诉我们,可以让
b
[
l
]
b[l]
b[l]加
x
x
x,
b
[
r
+
1
]
b[r+1]
b[r+1]减
x
x
x,查询
a
[
i
]
a[i]
a[i]的时候我们就理解为求
b
[
i
]
b[i]
b[i]的前缀和。
那现在我们要能实现单点修改,区间查询,怎么实现?
树状数组!对吧,问题就解决了。
这里同样有一道模版题
题目
洛谷P3368【模版】树状数组2
P3368题目通道
思路前面都讲过了,这里直接上代码吧
#include<iostream>
using namespace std;
int n,m,c[1000005],y;
int lowbit(int x){
return x&-x;
}
void add(int x,int v){
while(x<=n){
c[x]+=v;
x+=lowbit(x);
}
}
int sum(int x){
int ret=0;
while(x>0){
ret+=c[x];
x-=lowbit(x);
}
return ret;
}
int main(){
cin>>n>>m;
for(int i=1,x;i<=n;i++){
cin>>x;
add(i,x-y);
y=x;
}
for(int i=1,k,l,r,a;i<=m;i++){
cin>>k;
if(k==2){
cin>>a;
cout<<sum(a)<<endl;
}else{
cin>>l>>r>>a;
add(l,a);
add(r+1,-a);
}
}
return 0;
}
那现在我们只差 区间查询+一些题目了,对吧
区间查询
我们还是来看看我们要求的到底是什么吧,假如说
a
[
i
]
a[i]
a[i]的前缀和数组是
q
[
i
]
q[i]
q[i]
但注意,我们维护的并不是
a
[
i
]
a[i]
a[i],而是
b
[
i
]
b[i]
b[i]
q
[
x
]
=
∑
i
=
1
x
a
[
i
]
q[x]=\sum_{i=1}^x a[i]
q[x]=i=1∑xa[i]
q
[
x
]
=
∑
i
=
1
x
∑
j
=
1
i
b
[
j
]
q[x]=\sum_{i=1}^x \sum_{j=1}^i b[j]
q[x]=i=1∑xj=1∑ib[j]
∴
q
[
x
]
=
∑
i
=
1
x
(
x
−
i
+
1
)
b
[
i
]
∴q[x]=\sum_{i=1}^x (x-i+1)b[i]
∴q[x]=i=1∑x(x−i+1)b[i]
可是我们在查询前是不知道
x
x
x是多少的,那我们就没法直接用树状数组维护。
但我们发现稍微变一下就简单多了
∵
q
[
x
]
=
∑
i
=
1
x
(
x
−
i
+
1
)
b
[
i
]
∵q[x]=\sum_{i=1}^x (x-i+1)b[i]
∵q[x]=i=1∑x(x−i+1)b[i]
∴
q
[
x
]
=
(
x
+
1
)
∑
i
=
1
x
b
[
i
]
−
∑
i
=
1
x
i
b
[
i
]
∴q[x]=(x+1)\sum_{i=1}^x b[i]-\sum_{i=1}^x ib[i]
∴q[x]=(x+1)i=1∑xb[i]−i=1∑xib[i]
发现了吗?现在我们要维护的有两个
∑
i
=
1
x
b
[
i
]
\sum_{i=1}^x b[i]
i=1∑xb[i]和
∑
i
=
1
x
i
b
[
i
]
\sum_{i=1}^x ib[i]
i=1∑xib[i]
这个知识点的部分我还没找到有关题目,就先不放代码了。如果有找到的,可以放在评论区。然后前面的部分如果有比较好的题目的话,也可以放在评论区。
写在最后
感谢各位花了这么长的时间来看一个初二牲蒟蒻的博客。
我的博客