树状数组基础及运用

本文深入讲解了树状数组的基本原理及实现方式,包括如何通过二进制思想构造节点与其子树的关系,以及如何利用这种关系高效地进行区间更新和查询操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

#include <cstdio>    
#include <cstring>    
#define maxn 50047    
int c[maxn], a[maxn];   
int n,t;  
int Lowbit(int x)  // 2^k  
{  
    return x&(-x);  
}  
void update(int i, int x)//i点增量为x  
{  
    while(i <= n)  
    {  
        c[i] += x;  
        i += Lowbit(i);  
    }  
}  
int sum(int x)//区间求和 [1,x]  
{  
    int sum=0;  
    while(x>0)  
    {  
        sum+=c[x];  
        x-=Lowbit(x);  
    }  
    return sum;  
}  
    
int Getsum(int x1,int x2) //求任意区间和  
{   
    return sum(x2) - sum(x1-1);   
}    
int main()   
{   
    int i , j;  
    scanf("%d",&t);   
    int count = 0;   
    while(t--)   
    {   
        count++;   
        memset(a,0,sizeof(a));   
        memset(c,0,sizeof(c));   
        scanf("%d",&n);   
        for(i = 1; i <= n; i++) //i须从1开始  
        {   
            scanf("%d",&a[i]);   
            update(i,a[i]); //初始的人数  
        }     
        printf("Case %d:\n",count);   
        char oper[11];   
        while(scanf("%s",oper)==1)   
        {   
            if(strcmp(oper,"End")==0)   
                break;   
            scanf("%d%d",&i,&j);   
            if(strcmp(oper,"Query")==0)   
            {   
                printf("%d\n",Getsum(i,j));   
            }   
            if(strcmp(oper,"Add")==0) //表示第i个营地增加j个人  
            {   
                a[i] += j;   
                update(i,j);   
            }   
            if(strcmp(oper,"Sub")==0) //表示第i个营地减少j个人  
            {   
                a[i] -= j;   
                update(i,-j);   
            }   
        }   
    }   
    return 0;   
}   

状数组的基础是一个被构造出来的式子:C[i]=A[i]+A[i-1]+....+A[i-2^k+1];k代表i的二进制的最后连续0的个数 比如 对于1000和101000,k=3。至于这个式子是怎么被构造出来的,k为什么要代表这个。因为二进制的思想。

根据这个图来看节点与其子树的关系

接下来则很容易发现 节点和子节点的是有关系的,这种关系就是 i=j+lowbit(j); lowbit是j的最低位1所代表的数字 比如对于 1000(8的二进制) 1000=100+lowbit(100)=110+lowbit(110)=111+lowbit(111);

这个关系是树状数组的核心,有了这个关系,我们可以把子区间的变化以log2n的次数传递上去

那我们也知道了 当我们要求 1-n的和时,我们同样把n表示为2进制,我们知道 C[i]=A[i]+A[i-1]+....+A[i-2^k+1]; 所以对于i 是不是我们只要把他所有的1都用上 就可以表示1-n的和?

举个例子 求1-11000 则 Sum(11000)=C[11000]+C[10000]; 因为 根据C[i]的构造方法 C[i]是从A[i]开始的2^k个元素的和,则C[11000]求了A[11000] A[10111] A[10110] A[10101] A[10100] A[10010] A[10001] 这2^k个数 然后接着C[10000]求出了剩下 10000个元素的和 到了这我们就大概了解了树状数组的发明者的天才的构造是从何而来的了 普通的求1-n的和储存的数据太多,而这位天才则想,我们能不能根据二进制的思想来储存这些值呢?任何一个数,都可以由若干个二进制数相加而成,如果我们在求Sum(n)之前就知道了 n对应的二进制数从最低位开始,每个1所代表的数字的前2^i个数的和,我们不就能在时间复杂度log2(n)内求出所有的值 比如 101110 我们如果知道 101110->101101 101100->101001 101000->100001 100000->1各自的和,就可以在空间复杂度和时间复杂度很小的情况下求出1-101110了

然后对于区间和的修改 又利用每个小区间向上转移 修改了大区间的和

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值