树状数组

作用

log(n)的单点修改,log(n)求前缀和,因而可以快速进行区间查询

做法

顾名思义,树状数组本质上是一棵树,详见下图(网上找的).
原版树状数组
C数组表示要记录的数组,A数组表示每个点的数字,而在实际操作时只要记录C就行了,注意C[n]表示的并不是1~n的和.
上图可以转化为下图:
简化版树状数组
A,C数组间的关系见下图:
A,C数组间的关系

修改

当我们要修改某个点时,只要修改将让它加上它的二进制中最低数位上的1后所构成的数即可;例子如下:
1.共16个数要第三数加2,则让序号为下列数的数均+2.
3(00011)
4(00100=00011+00001)
8(01000=00100+100)
16(10000=01000+01000)
也就是下图中的红色部分:
这里写图片描述
2.共16个数要第9个数-3,则让序号为下列数的数均+2.
9(01001)
10(01010=01001+00001)
12(01100=01010+00010)
16(10000=01100+00100)
也就是下图红色部分:
这里写图片描述

区间查询

首先再次强调不是由C数组记录的,C数组只是用于辅助,8个数的C数组如下:
1=(001) C[1]=A[1];
2=(010) C[2]=A[1]+A[2];
3=(011) C[3]=A[3];
4=(100) C[4]=A[1]+A[2]+A[3]+A[4];
5=(101) C[5]=A[5];
6=(110) C[6]=A[5]+A[6];
7=(111) C[7]=A[7];
8=(1000) C[8]=A[1]+A[2]+A[3]+A[4]+A[5]+A[6]+A[7]+A[8];
因此可以通过几个数组的相加求出1~n的区间,用区间相减的方法即可求出中间某段的区间.
经过观察可以发现它与单点修改恰恰相反,只要将让它不断减去它二进制中最低数位上的1后所构成的数即可.例如:
16个数,要查找6~14的和.
首先算出1~5的和,也就是将C数组中的下列数相加:
5(00101)
4(00100=00101-00001)
再算出1~14的和,也就是将C数组中的下列数相加:
14(01110)
12(01100=01110-00010)
8(01000=01100-00100)
将两个和相减即可.

lowbit

因为树状数组中的操作都需要找到二进制形式中的最低位的1,因此可以用lowbit优化.
lowbit(n)=n&(-n).

代码(洛谷P3374)

#include<iostream>
#include<cstdio>
#define N 500100
using namespace std;

int n,m,sz[N];//sz就是C数组

inline int lb(int u)//lowbit
{
    return u&(-u);
}

inline void add(int u,int v)
{
    for(;u<=n;u+=lb(u))
        sz[u]+=v;
}

inline int sum(int u)
{
    int res=0;
    for(;u;u-=lb(u))
    {
        res+=sz[u];
    }
    return res;
}

int main()
{
    int i,j,p,x,y;
    cin>>n>>m;
    for(i=1;i<=n;i++)
    {
        scanf("%d",&p);
        add(i,p);
    }
    for(i=1;i<=m;i++)
    {
        scanf("%d",&p);
        if(p==1)
        {
            scanf("%d%d",&x,&y);
            add(x,y);
        }
        else
        {
            scanf("%d%d",&x,&y);
            printf("%d\n",sum(y)-sum(x-1));
        }
    }
}

拓展

当题目要求只进行区间修改和单点查询两种操作时,也可用树状数组完成,利用差分可以将区间修改改为单点修改,而单点查询就变成了求前缀和,正好可以用树状数组完成.

代码(洛谷P3368)

#include<iostream>
#include<cstdio>
#define N 500100
using namespace std;

int n,m,sz[N],last;

inline int lb(int u)
{
    return u&(-u);
}

inline void add(int u,int v)
{
    for(;u<=n;u+=lb(u)) sz[u]+=v;
}

inline int sum(int u)
{
    int res=0;
    for(;u;u-=lb(u))
    {
        res+=sz[u];
    }
    return res;
}

int main()
{
    register int i,j,p,x,y,z;
    cin>>n>>m;
    for(i=1;i<=n;i++)
    {
        scanf("%d",&p);
        add(i,p-last);
        last=p;
    }
    for(i=1;i<=m;i++)
    {
        scanf("%d",&p);
        if(p==1)
        {
            scanf("%d%d%d",&x,&y,&z);
            add(x,z),add(y+1,-z);
        }
        else
        {
            scanf("%d",&x);
            printf("%d\n",sum(x));
        }
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值