树状数组之前缀和

树状数组之前缀和

在这里插入图片描述

原理

  • 下标i 管辖的区域大小:i 对应的二进制保留最低位的1, 其它位全部置0。
  • c[i] 的父亲的下标:用lowbit(i) 表示i 的管辖区域,那么i+lowbit(i) 就表示i 父亲结点的下标
  • c[i] 左边的仅挨着自己管辖区间的点的下标:i−lowbit(i)

问题 1: 我们如何去求 lowbit(i) :i & (-i) 就会把 i 的二进制除最低位 1 外全部置 0。例如:

  • 所有的数字在内存中都是补码存储的
  • 数字 4 的补码是00000100数字
  • -4 的补码是11111100
       4&(−4)=00000100&(11111100b)=100b=4
    这个运算我们就叫做 lowbit(i), 正好可以得到下标 i 的管辖区域

问题 2: 如何求一段区域的和 (前缀和):

  • 如果我们S[12]=A[1]+A[2]+…+A[12]
  • 我们知道:
    C[12]=A[12]+A[11]+A[10]+A[9]
    因为:lowbit(12)=4, 表示C[12] 管辖大小:4
  • 12−lowbit(12)=12−4=8
    +lowbit(8)=8 所以 8−lowbit(8)=0
    所以: A[1]+A[2]+…+A[12]=C[12]+C[8]

核心操作

const int N = 100060 //数组大小

int n //动态变量输入的数组大小
int c[N],ans[N] //数组c是树状数组,ans是最后主函数输出的答案

int lowbit(int x) { return (x & -x); } //求c数组下标
int fa(int p)      { return (p+lowbit(p);} //求p的父节点
int left(int p)    { return (p-lowbit(p);} //求p的上一个管辖范围的最左节点的下标
int getsum(int n){ // 求区间0~x 的前缀和
    int sum = 0;
    while( n>0 ){
        sum += c[n];
        n = lowbit(n);
    }
    return sum;
}
void change(int x, int num){  //更新节点x为数据num
    while( x>=N ){ //节点x更新并且带动x之后的所有节点更新
        c[x]+=num;
        x += lowbit(x); 
    }
}

例题

P3374 【模板】树状数组 1

在这里插入图片描述

思路: 最基本的查询前缀和的问题

#include<iostream>
#include<string.h>
#include<stdio.h>
using namespace std;

#define Ios ios::sync_with_stdio(false) , cin.tie(0) , cout.tie(0);
#define mem(a,b) memset(a,b,sizeof(a))

const int N = 600050;
int c[N],ans[N];

int lowbit(int x){
    return (x & -x);
}
int getsum(int n){
    int res = 0;
    while(n>0){
        res+=c[n];
        n-=lowbit(n);
    }
    return res;
}
void change(int x,int num){
    while(x<=N){
        c[x]+=num;
        x+=lowbit(x);
    }
}

int main(){
    int n,m;
    //初始化数组
    mem(c,0);
    mem(ans,0);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){ //此处由于我们省去了取定义原始数组a,所以我们利用change函数初始化c数组,有点贪心的思想
        int t;
        scanf("%d",&t);  
        change(i,t);
    }
    for(int i=1;i<=m;i++){
        int flag,x,y;
        scanf("%d%d%d",&flag,&x,&y);
        if(flag == 1){
            change(x,y);
        }else if(flag == 2){
            printf("%d\n",getsum(y)-getsum(x-1)); //区间和等于右端点的前缀和减去左端点的前缀和
        }
    }
    return 0;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值