题目来源
以这道题为例子
首先我们知道树状数组是这样的;
图中的C数组就是我们的树状数组;
暴力建树
首先我们可以通过单点修改的方式,暴力建树,但是这种是 O ( n l o g n ) O(nlogn) O(nlogn)的,如下;
Code
void add(int u,int v){
for(int x = u; x<=n; x+=lowbit(x)){
tr[x] += v;
}
}
//暴力建树
void build(){
for(int i=1;i<=n;++i){
add(i,b[i]);
}
}
O(n)方法一
有一个规律;
即
C
[
i
]
=
a
[
x
−
l
o
w
b
i
t
(
x
)
+
1
,
x
]
C[i]=a[x-lowbit(x)+1,x]
C[i]=a[x−lowbit(x)+1,x]
对于原数组 a a a的每个端点 R R R,其树状数组覆盖的长度是 l o w b i t ( R ) lowbit(R) lowbit(R)
这样我们就可以通过前缀和来实现 O ( n ) O(n) O(n)的初始化
Code
void build(){
for(int i=1;i<=n;++i){
//tr[i] = [x-lowbit(x)+1,x]
//s是前缀和数组
tr[i] = s[i] - s[i-lowbit(i)];
}
}
O(n)方式二
在观察一下那张图,我们可以发现,对于每个节点的值,都是通过其儿子得到的;
而暴力版本之所以是 O ( n l o g n ) O(nlogn) O(nlogn)是因为重复计算了一些点,而我们如果按顺序枚举每个点且不重复,那么复杂度必然是 O ( n ) O(n) O(n)的
Code
void build(){
for(int i=1;i<=n;++i){
tr[i] += b[i];
int fa = i + lowbit(i);
if(fa <= n){
tr[fa] += tr[i];
}
}
}
例题完整代码
#include <iostream>
using namespace std;
const int N = 1e5+10;
typedef long long ll;
int n,m,a[N],b[N],s[N];
ll tr[N];
int lowbit(int x){
return x & -x;
}
ll query(int x){
ll ret = 0;
for(int i=x;i;i-=lowbit(i)){
ret += tr[i];
}
return ret;
}
void add(int u,int v){
for(int x = u; x<=n; x+=lowbit(x)){
tr[x] += v;
}
}
void build(){
for(int i=1;i<=n;++i){
tr[i] += b[i];
int fa = i + lowbit(i);
if(fa <= n){
tr[fa] += tr[i];
}
}
}
int main(){
cin >> n >> m;
for(int i=1;i<=n;++i){
cin >> a[i];
b[i] = a[i] - a[i-1];
s[i] = b[i] + s[i-1];
}
build();
char ch;
while(m--){
cin >> ch;
if(ch == 'C'){
int l,r,v;
cin >> l >> r >> v;
add(l,v);
add(r+1,-v);
}else{
int x;
cin >> x;
cout << query(x) << '\n';
}
}
return 0;
}