树状数组的问题处理

树状数组能够很容易求出前缀和

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\n" , 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 }
POJ3648

 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\n" , query(s,t));
40             else update(s,t);
41         }
42     }
43     return 0;
44 }
HDU1754 I hate it

 

转载于:https://www.cnblogs.com/CSU3901130321/p/5337938.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值