树状数组能够很容易求出前缀和
1.对于区间更新问题
我们要借用辅助函数来帮助自己
自定义一个函数add[i] , 表示将i~n的数都加了add[i]的值
那么将[s,t]区间都增加v,就很容易理解为add[s]+=v , add[t+1]-=v了
查询区间[s,t]的总和的时候就可以理解为:
ans = sum(t) - sum(s-1)
这里只要考虑sum(x)怎么求解即可
比如原来前i个数保存在a[]中
那么
这就很容易理解为是用3个树状数组求一个a[i],一个add[i],一个i*add[i]的前缀和了
当然这里也可以把a[i]和-add[i]*i合并在一起求前缀和~
1 #include <cstdio> 2 #include <cstring> 3 using namespace std; 4 #define N 100010 5 #define lowbit(x) (x&(-x)) 6 #define ll long long 7 ll sum[N] , add[N] , sub[N]; 8 int n,q; 9 void initAdd(int p , int v) 10 { 11 for(int x=p ; x<=n ; x+=lowbit(x)) sum[x]=sum[x]+v; 12 } 13 void update(int s , int t , int v) 14 { 15 for(int x=s ; x<=n ; x+=lowbit(x)){ 16 add[x]+=v; 17 sub[x]+=(ll)s*v; 18 } 19 for(int x=t+1 ; x<=n ; x+=lowbit(x)){ 20 add[x]-=v; 21 sub[x]-=(ll)v*(t+1); 22 } 23 } 24 ll query(int s , int t) 25 { 26 ll ans = 0; 27 for(int x=t ; x>0 ; x-=lowbit(x)) ans+=sum[x]+(ll)(t+1)*add[x]-sub[x]; 28 for(int x=s-1 ; x>0 ; x-=lowbit(x)) ans-=sum[x]+(ll)s*add[x]-sub[x]; 29 return ans; 30 } 31 int main() 32 { 33 // freopen("a.in" , "r" , stdin); 34 while(~scanf("%d%d" , &n , &q)){ 35 memset(sum,0,sizeof(ll)*(n+1)); 36 memset(add,0,sizeof(ll)*(n+1)); 37 memset(sub,0,sizeof(ll)*(n+1)); 38 int x; 39 for(int i=1; i<=n ; i++){ 40 scanf("%d" , &x); 41 initAdd(i,x); 42 } 43 char op[2]; 44 while(q--){ 45 scanf("%s" , op); 46 int a,b,c; 47 if(op[0]=='Q'){ 48 scanf("%d%d" , &a , &b); 49 printf("%I64d " , query(a,b)); 50 }else{ 51 scanf("%d%d%d" , &a , &b , &c); 52 update(a , b , c); 53 } 54 } 55 } 56 return 0; 57 }
2.树状数组求解区间极值问题
静态求区间极大极小可以用MST或者线段树做,同样也可以用树状数组像MST一样写出精简的代码
另外他比MST更强的是,他同样可以处理在单点更新下的问题
对于一个数组a[],如何初始化对应的树状数组:
对于每一个树状数组上的点,我们要思考它往下走到的是树上的哪些点,对于每一个树状数组上的点i来说
它所有的儿子其实是lowbit(i)的位数-1
所以这么表示for(int x=1 ; x<lowbit(i) ; x<<=1) //operation;
但这里注意的是这里 i-x 才是它的儿子节点
所以初始化就是
void init() { for(int i=1; i<=n ; i++){ Max[i]=a[i]; for(int x=1 ; x<lowbit(i) ; x<<=1) Max[i]=max(Max[i],Max[i-x]); } }
简单的点更新只要去不断往上更新树上的父亲节点直到结束即可
void update(int p , int v) { a[p] = v; for(int x=p ; x<=n ; x+=lowbit(x)) Max[x] = max(Max[x] , v); }
区间最大值的询问可以很容易的思考,如果从当前位置pos出发,可以走最长的lowbit(pos)就是这个pos点可覆盖的长度后还在区间范围内就说明是可行的,我们当然要走最大的,否则就只移动一格就可以了- -,这个移动一格的次数不会多的,所以不要误认为这里会超时
int query(int s , int t) { int ans = a[t]; for(int x=t-1 ; x>=s ; ){ if(x-lowbit(x)>=s-1) ans = max(ans , Max[x]) , x-=lowbit(x); else ans = max(ans , a[x]) , x--; } return ans; }
1 #include <cstdio> 2 #include <cstring> 3 #include <iostream> 4 using namespace std; 5 #define N 200000 6 #define lowbit(x) (x&(-x)) 7 #define max(a,b) a>b?a:b 8 int a[N+2] , Max[N+2] , n , m; 9 void init() 10 { 11 for(int i=1; i<=n ; i++){ 12 Max[i]=a[i]; 13 for(int x=1 ; x<lowbit(i) ; x<<=1) Max[i]=max(Max[i],Max[i-x]); 14 } 15 } 16 void update(int p , int v) 17 { 18 a[p] = v; 19 for(int x=p ; x<=n ; x+=lowbit(x)) Max[x] = max(Max[x] , v); 20 } 21 int query(int s , int t) 22 { 23 int ans = a[t]; 24 for(int x=t-1 ; x>=s ; ){ 25 if(x-lowbit(x)>=s-1) ans = max(ans , Max[x]) , x-=lowbit(x); 26 else ans = max(ans , a[x]) , x--; 27 } 28 return ans; 29 } 30 int main() 31 { 32 // freopen("a.in" , "r" , stdin); 33 while(~scanf("%d%d" , &n , &m)){ 34 for(int i=1 ;i<=n ; i++) scanf("%d" , &a[i]); 35 init(); 36 char op[2]; int s,t; 37 while(m--){ 38 scanf("%s%d%d", op , &s , &t); 39 if(op[0]=='Q') printf("%d " , query(s,t)); 40 else update(s,t); 41 } 42 } 43 return 0; 44 }