树状数组详解
树状数组是一种特殊的数据结构,假设我们有个数组a,建立的树状数组C,能够快速完成下述操作:
- 计算前缀和,给定 i,计算
- 更改某个位置的值,给定 pos 和 x ,执行
- 计算区间和,给定区间 l 和 r ,计算
需要建立的树状数组如下图所示:
得到了树状数组的图,很容易发现:
c[1] = a[1]
c[2] = a[1] + a[2]
c[3] = a[3]
c[4] = a[1] + a[2] + a[3] + a[4]
c[5] = a[5]
c[6] = a[5] + a[6]
c[7] = a[7]
c[8] = a[1] + a[2] + a[3] + a[4] + a[5] + a[6] + a[7] + a[8]
c[9] = a[9]
在上述的规律中,其中 c [ x ] 保存的是序列 a 的区间 [ x - lowbit (x) + 1 , x ] 的和。
性质:任意整数的可以被 2 的不重复次幂分解。
比如: , , 等等……
所以,若结尾为 R ,则区间长度就等于 R 的 “二进制分解” 下最小的 2 的次幂,即 lowbit(R)
放到树状数组上,即向前取 lowbit(R) 位,就是c的值
lowbit 运算即非负整数下,某个数的二进制表示下最低位的 1 及后续的 0 组成的数值。
深入了解lowbit运算,请戳 ------》 深入理解并证明 lowbit 运算
1 . 计算前缀和
分析:不断向前取位,然后累加即可
int ask(int pos){
int res = 0;
while(res){
res += c[pos];
pos -= lowbit(pos);
}
return pos;
}
2 . 更改某个位置的值
分析:若要更改某个位置的值,因为是树状结构,所以更改完最底层的值,需要向上依次寻找他的父亲,直到 pos 小于总个数即可
void update(int pos,int k){
while(k<=n){
c[pos] += k;
pos += lowbit(pos);
}
}
3 . 计算区间和
分析:计算区间 [ l , r ] 的和,只需求位置 r 与位置 l - 1 的前缀和,然后相减即可
void query(int l,int r){
return ask(r) - ask(l-1);
}
下面放一道模板题:【链接】
AC代码:
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <stack>
#include <vector>
#include <string>
#include <map>
#include <set>
#include <algorithm>
using namespace std;
#define INF 1 << 30
#define MAXN 500000+5
#define sscc ios::sync_with_stdio(false)
#define mem(a,b) memset(a,b,sizeof(a))
#define mpy(a,b) memcpy(a,b,sizeif(a))
#define lowbit(x) ((x)&(-x))
typedef long long ll;
typedef unsigned long long ull;
int n,m;
int c[MAXN];
void update(int x,int k){
while(x<=n){
c[x]+=k;
x+=lowbit(x);
}
}
int ask(int pos){
int res=0;
while(pos){
res+=c[pos];
pos-=lowbit(pos);
}
return res;
}
int query(int l,int r){
return ask(r)-ask(l-1);
}
int main(){
sscc;
mem(c,0);
cin >> n >> m;
for(int i=1;i<=n;i++){
int a;
cin >> a;
update(i,a);
}
while(m--){
int a,b,c;
cin >> a >> b >> c;
if(a==1)
update(b,c);
if(a==2)
cout << query(b,c) << endl;
}
return 0;
}