树状数组总结

参考博客1
参考博客2

什么是树状数组

就是用数组来模拟树形结构。和线段树很像,但是代码比较简单,能做的事也比较少。
在这里插入图片描述

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[i] = A[i - 2k+1] + A[i - 2k+2] + … + A[i]; //k为i的二进制中从最低位到高位连续零的长度
※※前i项和:SUMi = C[i] + C[i-2k1] + C[(i - 2k1) - 2k2] + …;

如何转化成代码

(1)如何求2k:2k= i&(-i)//运用到计算机组成补码的知识,可得证
(2)
①对于c[i],假设我们改变A[i],那么要改变的c[i]有 C[i + 2k]、C[(i + 2k1) + 2k2]…所以可以用A[i]来求c[i];②要求sum,就是上面所说的前i项和
其实我们可以这样想:对于要改变的c[i],所谓c[i+2k],就是把最末位的1变成0,然后往前进位:如
A[i]=1010100
要改变的c有
c[i+2k]=1011000
c[i+2k1+2k2]=1100000
……
这样子我们才能保证c[i-2k+1]小于A[i],即c[i]里面包含A[i]
※※同理,我们要求sum[i],就是对末尾1开始往左所有的1依次减0;
eg.
如13的二进制表示为1101,那么-13的二进制而将13二进制按位取反,然后末尾+1,即0010 + 0001 = 0011,那么1101 & 0011== 0001,很显然得到m == 13二进制末尾1的位置是2的0次方位,将13 - 0001 == 12,再对12执行lowbit操作,1100 & 0100 == 0100,也很轻易得到了m == 12时二进制末尾1的位置是2的2次方位,将12 - 0100 == 8,再对8执行lowbit操作,0100 & 1100 == 0100,得到m == 8时二进制位是2的2次方位,8 - 0100 == 0(结束操作),通过循环得到的13,12,8,则sum(13) == C13 + C12 + C8
※※※※
简化一下就是:
13:1101(从1101到1101)
12:1100(从1001到1100)
8:1000(从0到1000)
加起来就刚好是0到1101
※※※※

 1 int n;
 2 int a[1005],c[1005]; //对应原数组和树状数组
 3 
 4 int lowbit(int x){
 5     return x&(-x);
 6 }
 7 
 8 void updata(int i,int k){    //在i位置加上k
 9     while(i <= n){
10         c[i] += k;
11         i += lowbit(i);
12     }
13 }
14 
15 int getsum(int i){        //求A[1 - i]的和
16     int res = 0;
17     while(i > 0){
18         res += c[i];
19         i -= lowbit(i);
20     }
21     return res;
22 }

模板

HDU-1166
第一行一个整数T,表示有T组数据。
input:每组数据第一行一个正整数N(N<=50000),表示敌人有N个工兵营地,接下来有N个正整数,第i个正整数ai代表第i个工兵营地里开始时有ai个人(1<=ai<=50)。
接下来每行有一条命令,命令有4种形式:
(1) Add i j,i和j为正整数,表示第i个营地增加j个人(j不超过30)
(2)Sub i j ,i和j为正整数,表示第i个营地减少j个人(j不超过30);
(3)Query i j ,i和j为正整数,i<=j,表示询问第i到第j个营地的总人数;
(4)End 表示结束,这条命令在每组数据最后出现;
每组数据最多有40000条命令
output:对第i组数据,首先输出“Case i:”和回车,
对于每个Query询问,输出一个整数并回车,表示询问的段中的总人数,这个数保持在int以内。

#include <bits/stdc++.h>
using namespace std;

int n,m;
int a[50005],c[50005]; //对应原数组和树状数组

int lowbit(int x){
    return x&(-x);
}

void updata(int i,int k){    //在i位置加上k
    while(i <= n){
        c[i] += k;//改变a[i],枚举所有要改变的的c[i]
        i += lowbit(i);
    }
}

int getsum(int i){        //求A[1 - i]的和
    int res = 0;
    while(i > 0){
        res += c[i];
        i -= lowbit(i);
    }
    return res;
}

int main(){
    int t;
    cin>>t;
    for(int tot = 1; tot <= t; tot++){
        cout << "Case " << tot << ":" << endl;
        memset(a, 0, sizeof a);
        memset(c, 0, sizeof  c);
        cin>>n;
        for(int i = 1; i <= n; i++){
            cin>>a[i];
            updata(i,a[i]);   //输入初值的时候,也相当于更新了值
        }

        string s;
        int x,y;
        while(cin>>s && s[0] != 'E'){
            cin>>x>>y;
            if(s[0] == 'Q'){    //求和操作
                int sum = getsum(y) - getsum(x-1);    //x-y区间和也就等于1-y区间和减去1-(x-1)区间和
                cout << sum << endl;
            }
            else if(s[0] == 'A'){
                updata(x,y);
            }
            else if(s[0] == 'S'){
                updata(x,-y);    //减去操作,即为加上相反数
            }
        }

    }
    return 0;
}

更多问题

一:区间增,单点求和
运用到前缀和的知识,上述我们的a[i]是数组本身的值,这里我们用i与i-1的差值来代替其本身,记为数组D(D[0]=0)
那么对a[i]来说,D[i]的前缀和就是a[i]
还是求前缀和所以代码不变。只是输入做改变。
也就是update(x,a[i])改成了update(x,a[i]-a[i-1])

int n,m;
int a[50005] = {0},c[50005]; //对应原数组和树状数组

int lowbit(int x){
    return x&(-x);
}

void updata(int i,int k){    //在i位置加上k
    while(i <= n){
        c[i] += k;
        i += lowbit(i);
    }
}

int getsum(int i){        //求D[1 - i]的和,即A[i]值
    int res = 0;
    while(i > 0){
        res += c[i];
        i -= lowbit(i);
    }
    return res;
}

int main(){
    cin>>n;27     for(int i = 1; i <= n; i++){
        cin>>a[i];
        updata(i,a[i] - a[i-1]);   //输入初值的时候,也相当于更新了值
    }
    
    //[x,y]区间内加上k
    updata(x,k);    //A[x] - A[x-1]增加k
    updata(y+1,-k);        //A[y+1] - A[y]减少k
    
    //查询i位置的值
    int sum = getsum(i);

    return 0;
}

二、区间修改区间求和
∑ni = 1A[i] = ∑ni = 1 ∑ij = 1D[j];
则A[1]+A[2]+…+A[n]
= (D[1]) + (D[1]+D[2]) + … + (D[1]+D[2]+…+D[n])
= nD[1] + (n-1)D[2] +… +D[n]
= n * (D[1]+D[2]+…+D[n]) - (0
D[1]+1
D[2]+…+(n-1)*D[n])

所以上式可以变为∑ni = 1A[i] = n∑ni = 1D[i] - ∑ni = 1( D[i](i-1) );
如果你理解前面的都比较轻松的话,这里也就知道要干嘛了,维护两个数状数组,sum1[i] = D[i],sum2[i] = D[i]*(i-1);

int n,m;
int a[50005] = {0};
int sum1[50005];    //(D[1] + D[2] + ... + D[n])
int sum2[50005];    //(1*D[1] + 2*D[2] + ... + n*D[n])

int lowbit(int x){
    return x&(-x);
}

void updata(int i,int k){
    int x = i;    //因为x不变,所以得先保存i值
    while(i <= n){
        sum1[i] += k;
        sum2[i] += k * (x-1);
        i += lowbit(i);
    }
}

int getsum(int i){        //求前缀和
    int res = 0, x = i;
    while(i > 0){
        res += x * sum1[i] - sum2[i];
        i -= lowbit(i);
    }
    return res;
}

int main(){
    cin>>n;
    for(int i = 1; i <= n; i++){
        cin>>a[i];
        updata(i,a[i] - a[i-1]);   //输入初值的时候,也相当于更新了值
    }

    //[x,y]区间内加上k
    updata(x,k);    //A[x] - A[x-1]增加k
    updata(y+1,-k);        //A[y+1] - A[y]减少k

    //求[x,y]区间和
    int sum = getsum(y) - getsum(x-1);

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值