树状数组模板

简介: 树状数组模板

一、概述

1、求区间元素和、修改某个位置的值 O(logn)
2、常数小于线段树,代码更短
3、应用范围比线段树广

二、构建

在这里插入图片描述
满二叉树,C[i]为A[i]对应的那一列的最高的节点

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];

十进制转化为二进制:
1 00000001
2 00000010
3 00000011
4 00000100
5 00000101
6 00000110
7 00000111
8 00001000

注意每种结尾中0的个数:
以1结尾的节点C[i]代表A[i],表示的区间长度为1(2^0)
以10结尾的节点C[i]代表A[i]+A[i-1],表示的区间长度为2(2^1)
以100结尾的节点C[i]代表A[i]+A[i-1]+A[i-2]+A[i-3],表示的区间长度为4(2^2)
以1000结尾的节点C[i]代表A[i]+A[i-1]+A[i-2]+A[i-3]+A[i-4]+A[i-5]+A[i-6]+A[i-7],表示的区间长度为8(2^3)

因此可以扩展归纳为:如果i的二进制中从最低位开始向最高位数有连续的x个0,那么C[i]拥有2^x个叶子?,表示的区间范围为A[i]~A[i-2^x+1]

三、求和
返回前i个元素的和

返回前i个元素的和
int Sum(int i){
    int s=0;
    while(i>0){
        s+=C[i];
        i-=i&(-i);
    }
    return s;
}

四、更新数组

将A[i]的值改为value

void Update(int i,int value){
    while(i<=n){
        C[i]+=value;
        i+=i&(-i);
    }
}

*五、
1.【洛谷】P3374 【模板】树状数组 1*

#include<cstdio>
using namespace std;
int C[500006],N,M;
void Update(int i,int value){
    while(i<=N){
        C[i]+=value;
        i+=i&(-i);
    }
}
int Sum(int i){
    int s=0;
    while(i>0){
        s+=C[i];
        i-=i&(-i);
    }
    return s;
}
int main(int argc, char const *argv[])
{
    scanf("%d%d",&N,&M);
    int number,cz,x,y;
    for(int i=1;i<=N;++i) {scanf("%d",&number);Update(i,number);}
    for(int i=1;i<=M;++i) {
        scanf("%d",&cz);
        if(cz>>1){
            scanf("%d%d",&x,&y);
            printf("%d\n",Sum(y)-Sum(x-1));
        }
        else{
            scanf("%d%d",&x,&y);
            Update(x,y);
        }
    }
    return 0;
}

2.【洛谷】P3368 【模板】树状数组 2

方法一、

在树状数组1中,Update操作会将从i之后的前i个元素和、前i+1个元素和······前N个元素和发生改变。在树状数组2中为了保证修改从x~y范围内的每一项而不影响后面项,通过Update(y+1,-k)来抵消对之后的影响(把多增加的再减回去)

#include<cstdio>
using namespace std;
int C[1926081],N,M;
int Sum(int i){
    int s=0;
    while(i>0){
        s+=C[i];
        i-=i&(-i);
    }
    return s;
}
void Update(int i,int value){
    while(i<=N){
        C[i]+=value;
        i+=i&(-i);
    }
}
int main(){
    scanf("%d%d",&N,&M);
    int number,cz,x,y,k;
    for(int i=1;i<=N;++i){scanf("%d",&number);Update(i,number);Update(i+1,-number);}
    for(int i=1;i<=M;++i){
        scanf("%d",&cz);
        if(cz==2){
            scanf("%d",&x);
            printf("%d\n",Sum(x));
        }
        else{
            scanf("%d%d%d",&x,&y,&k);
            Update(x,k);Update(y+1,-k);
        }
    }
    return 0;
}

方法二、

树状数组+差分:
开两个数组:a[],C[]。a[]存原来的数,C[]用于差分。存入C[]时用Update(i,a[i]-a[i-1]);
树状数组中前i个数的和表示第i个数
修改x~y区间时只需在树状数组中将x+k,y+1-k即可
询问第i个数时直接用Sum(i)输出

#include<cstdio>
using namespace std;
int C[7180629],a[1926081],N,M;
void Update(int i,int k){
    while(i<=N){
        C[i]+=k;
        i+=i&(-i);
    }
}
int Sum(int i){
    int s=0;
    while(i>0){
        s+=C[i];
        i-=i&(-i);
    }
    return s;
}
int main(){
    int cz,x,y,k;
    scanf("%d%d",&N,&M);
    for(int i=1;i<=N;++i){
        scanf("%d",&a[i]);
        Update(i,a[i]-a[i-1]);
    }
    for(int i=1;i<=M;++i){
        scanf("%d",&cz);
        if(cz==1){
            scanf("%d%d%d",&x,&y,&k);
            Update(x,k);
            Update(y+1,-k);
        }
        else{
            scanf("%d",&x);
            printf("%d\n",Sum(x));
        }
    }
    return 0;
}
目录
相关文章
|
7月前
树状数组模板
树状数组模板
43 0
|
5月前
|
算法
二分 模板
二分的另一个板子
39 1
|
7月前
|
Python
{二分模板}
{二分模板}
28 0
|
7月前
线段树模板
线段树模板
46 0
|
SQL 人工智能 开发框架
线段树模板+例题
线段树模板+例题
78 1
洛谷—模板字典树 P8306
洛谷—模板字典树 P8306
88 0
|
算法
树状数组模板与练习
树状数组模板与练习
102 0
|
存储 算法
线段树模板与练习
线段树模板与练习
106 0
二分搜索的三种模板
二分搜索的三种模板
67 0