查询和更新的复杂度均为 O(log n)。
单点更新,单点查询
传统数组直接可以搞定。
单点更新,区间查询
树状数组来完成。
#define MAX 100
int n; //题目实际输入的树状数组的大小
int init[MAX], tree[MAX]; //初始数据存放数组,树状数组
int lowbit(int position){ //计算下一个position关联位置
return position & (-position);
}
void update(int position, int add_num){ //更新tree[position]的值,使其加上add_num
while(position <= n){ //position位置数据更新只会影响到position及其后数据
tree[position] += add_num;
position += lowbit(position);
}
}
int sum(int position){ //求数组init[]中前position个数据之和
int res = 0;
while(position > 0){ //求前position个和,position需要减小
res += tree[position];
position -= lowbit(position);
}
return res;
}
/*---使用方法---*/
update(position, add_num); //单点更新
res = sum(right) - sum(left - 1); //查询区间[left, right]间数据和
区间更新,单点查询
此时直接对区间内数据都进行更新,将导致复杂度上升,因此引入差分形式的树状数组。举例:对于数据init[] = 1 3 4 6 8
构建差分形式的树状数组diff_tree[] = 1 2 1 2 2
,其中position位置的数据值为diff_tree[position] = init[position] - init[position - 1]
,其中约定init[0] = 0
。当区间[2,4]
间的数据同时加3时,此时差分形式的树状数组变为diff_tree[] = 1 5 1 2 -1
,观察到只有2位置和5位置的数据进行了更新,因此将一个区间的更新变为仅更新两个单点,极大的减小了复杂度。而由于仅需单点查询,因此将树状数组中前position个和加起来即为第position个数据的值,即8 = init[5] = 1 + 5 + 1 + 2 + (-1) = diff_tree[1] + diff_tree[2] + ... + diff_tree[5]
具体实现为:
#define MAX 100
int n; //题目实际输入的树状数组的大小
int init[MAX], diff_tree[MAX]; //初始数据存放数组,树状数组
/*---三个关键函数和普通树状数组保持一致---*/
int lowbit(int position){
return position & (-position);
}
void update(int position, int add_num){
while(position <= n){
diff_tree[position] += add_num;
position += lowbit(position);
}
}
int sum(int position){
int res = 0;
while(position > 0){
res += diff_tree[position];
position -= lowbit(position);
}
return res;
}
/*---使用方法---*/
update(left, add_num); //区间最左侧的点加上add_num
update(right + 1, -add_num); //区间右侧点的后一个值减去add_num
res = sum(position); //查询position位置的值
区间更新,区间查询
由于存在区间更新,因此仍使用差分形式的树状数组。计算前position个数据和即为:
∑
i
=
1
p
o
s
i
t
i
o
n
i
n
i
t
[
i
]
=
∑
i
=
1
p
o
s
i
t
i
o
n
∑
j
=
1
i
d
i
f
f
_
t
r
e
e
[
j
]
=
n
×
d
i
f
f
_
t
r
e
e
[
1
]
+
(
n
−
1
)
×
d
i
f
f
_
t
r
e
e
[
2
]
+
⋯
+
d
i
f
f
_
t
r
e
e
[
p
o
s
i
t
i
o
n
]
=
n
×
(
d
i
f
_
t
r
e
e
[
1
]
+
d
i
f
f
_
t
r
e
e
[
2
]
+
⋯
+
d
i
f
f
_
t
r
e
e
[
p
o
s
i
t
i
o
n
]
)
−
(
0
×
d
i
f
f
_
t
r
e
e
[
1
]
+
1
×
d
i
f
f
_
t
r
e
e
[
2
]
+
⋯
+
(
n
−
1
)
×
d
i
f
f
_
t
r
e
e
[
p
o
s
i
t
i
o
n
]
)
=
n
×
∑
i
=
1
p
o
s
i
t
i
o
n
d
i
f
f
_
t
r
e
e
[
i
]
−
∑
i
=
1
p
o
s
i
t
i
o
n
(
d
i
f
f
_
t
r
e
e
[
i
]
×
(
i
−
1
)
)
\begin{aligned} \sum_{i = 1}^{position}{init[i]}=&\sum_{i = 1}^{position}{\sum_{j = 1}^{i}{diff\_tree[j]}} \\ =&\ n\times diff\_tree[1] + (n - 1)\times diff\_tree[2] +\dots +diff\_tree[position] \\ =&\ n\times(dif\_tree[1] + diff\_tree[2] +\dots+diff\_tree[position])\\ &-(0\times diff\_tree[1] + 1\times diff\_tree[2]+\dots+(n-1)\times diff\_tree[position]) \\ =&\ n\times \sum_{i = 1}^{position}{diff\_tree[i]}-\sum_{i=1}^{position}{(diff\_tree[i]\times (i-1))} \end{aligned}
i=1∑positioninit[i]====i=1∑positionj=1∑idiff_tree[j] n×diff_tree[1]+(n−1)×diff_tree[2]+⋯+diff_tree[position] n×(dif_tree[1]+diff_tree[2]+⋯+diff_tree[position])−(0×diff_tree[1]+1×diff_tree[2]+⋯+(n−1)×diff_tree[position]) n×i=1∑positiondiff_tree[i]−i=1∑position(diff_tree[i]×(i−1))
因此维护两个数组sum_tree[i] = diff_tree[i]
和sum_plus_tree[i] = diff_tree[i]*(i - 1)
。具体实现如下:
#define MAX 100
int n; //题目实际输入的树状数组的大小
int init[MAX]; //初始数据存放数组
int sum_tree[MAX]; //存储diff_tree[1] + diff_tree[2] + ... + diff_tree[n]
int sum_plus_tree[MAX]; //存储diff_tree[1]*0 + diff_tree[2]*1 + ... +diff_tree[n]*(n - 1)
/*---三个关键函数发生了更改---*/
int lowbit(int position){
return position & (-position);
}
void update(int position, int add_num){
int num = position; //num值在更新其他关联位置时不变
while(position <= n){
sum_tree[position] += add_num;
sum_plus_tree[position] += add_num * (num - 1);
position += lowbit(position);
}
}
int sum(int position){
int res = 0, num = position;
while(position > 0){
res += num * sum_tree[position] - sum_plus_tree[position];
position -= lowbit(position);
}
return res;
}
/*---使用方法---*/
update(left, add_num); //区间最左侧的点加上add_num
update(right + 1, -add_num); //区间右侧点的后一个值减去add_num
res = sum(right) - sum(left - 1); //求区间[left, right]间数据和
前缀和
S
i
S_i
Si记录前
i
i
i个数据和,求
∑
i
=
1
p
o
s
S
i
\sum_{i=1}^{pos} S_i
∑i=1posSi。此时公式变为
∑
i
=
1
p
o
s
S
i
=
(
n
+
1
)
×
∑
i
=
1
p
o
s
i
t
i
o
n
t
r
e
e
[
i
]
−
∑
i
=
1
p
o
s
i
t
i
o
n
(
t
r
e
e
[
i
]
×
i
)
\sum_{i=1}^{pos} S_i=(n+1)\times \sum_{i = 1}^{position}{tree[i]}-\sum_{i=1}^{position}{(tree[i]\times i)}
i=1∑posSi=(n+1)×i=1∑positiontree[i]−i=1∑position(tree[i]×i)
#include <iostream>
typedef long long ll;
const int nm_max = 1e5 + 5;
int n;
ll init[nm_max];
ll sum_tree[nm_max]; //存储sum_tree[n]=tree[1] + tree[2] + ... + tree[n]
ll sum_plus_tree[nm_max]; //存储sum_plus_tree[n]=tree[1]*1 + tree[2]*2 + ... +tree[n]*n
/*---三个关键函数发生了更改---*/
int lowbit(int position){
return position & (-position);
}
void update(int position, ll add_num){
int num = position; //num值在更新其他关联位置时不变
while(position <= n){
sum_tree[position] += add_num;
sum_plus_tree[position] += add_num * num;
position += lowbit(position);
}
}
ll sum(int position){
ll res = 0;
int num = position;
while(position > 0){
res += (num + 1) * sum_tree[position] - sum_plus_tree[position];
position -= lowbit(position);
}
return res;
}
int main(){
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; ++i){
scanf("%lld", &init[i]);
update(i, init[i]);
}
for(int i = 1; i <= m; ++i){
scanf("%s", ch);
if(ch[0] == 'Q'){ //查询
scanf("%lld", &x_num);
printf("%lld\n", sum(x_num));
}
else if(ch[0] == 'M'){ //更新
scanf("%lld%lld", &pos, &x_num);
update(pos, x_num - init[pos]);
init[pos] = x_num;
}
}
return 0;
}