初见 | 数据结构 | 树状数组

前言

好久没发博客了啊,今天划个水发一个,

实际上看我最近的题解和博客可以发现我文章的格式变得规整了起来。

因为发着一篇愉悦一下,所以语言可能生动一点。

前置芝士

🍞🍕🍞两面包夹芝士!(雾)

请大家放心,下面还是 C++ 语言,而不是 M++ 语言。(笑)

One

引入性质:任意正整数都能被 2 的不重复次幂唯一分解。

即对于任意的 \(n \in N^{+}\) ,都可以被分解为如下的形式: \(n=2^{s_1}+2^{s_2}+\cdots+2^{s_k}\) ,其中 \(s_1 \ne s_2 \ne \cdots \ne s_k\)

我们若把这个和一个数在二进制下的表示联系起来,就能发现 n 在二进制下是 1 的位是第几位,一一对应这上面的 si

Two

刚才说到了求 n 在二进制下的 1 的位数,于是我们就引出下面这个运算:\(\text{lowbit}(x)\)

\(\text{lowbit}(x)\) 代表的意思是 x 在二进制下最低位的 1 和其后面的 0 组成的数。

那么根据计算机存储数字时使用的补码和反码的特点,我们可以直接简单的得到下面这个进行 \(\text{lowbit}\) 运算的方法。(我一般直接 define 掉,写函数当然也是可以的)

#define lowbit ((x)&(-x))

想要详细解释的同学们可以去搜一下,因为这种东西我不太行。

Three

我们参照上面的一些东西,再引入一个东西😋

对于一个区间 \([1,x]\) ,我们可以对它进行这样的分解。

先将 x 分解为 \(x=2^{i_1}+2^{i_2}+\cdots+2^{i_m}\),然后就能原来的区间分成 \(O(\log x)\) 个小区间:

\([1,2^{i_1}],[2^{i_1}+1,2^{i_1}+2^{i_2}],\cdots,[2^{i_1}+\cdots+2^{i_{m-1}}+1,2^{i_1}+\cdots+2^{i_{m}}]\)

啊那为什么要分成这样的小区间呢?

那肯定是有性质啊(

这些小区间满足这样一个听起来很玄学的性质:对于任意一个区间的右端 R ,这个区间的长度是和这个 R 相关哒,准确来说,就是 \(\text{lowbit}(R)\)

有没有很玄学的感觉?其实自己手模几个再简单证一证好像就能出来?

树状数组简介

首先我们需要知道树状数组是解决一些区间问题的,

然后自然刚才讲到的东西要用到的啦,树状数组维护的区间就是这样划分的~

要不然我说那么一大堆干啥

然后它大约就张这个样子:

于是大家应该就能明白什么是树状数组了吧(

树状数组是建立在一个序列上的,一般维护前缀和,可以进行单点修改和一般的查询区间和。

如果稍微做一些改动,比如在原序列的差分序列上建立的树状数组或者是进行一些其他操作(下面的 Pro ),能进行一定的区间增加操作。

实现

作为一个 DS ,如何实现她肯定是个重要的问题(笑)。

实际上相对于线段树而言,树状数组的码量算是少得多了(

这里把我的缺省源和快读快输放在这里,免得下面再重复复制占用太多的篇幅~

#include <bits/stdc++.h>
#define Heriko return
#define Deltana 0
#define Romano 1
#define S signed
#define U unsigned
#define LL long long
#define R register
#define I inline
#define D double
#define LD long double
#define LOWBIT(x) ((x)&(-x))
#define mst(a, b) memset(a, b, sizeof(a))
#define ON std::ios::sync_with_stdio(false)
using namespace std;
I void fr(LL & x)
{
    LL f = 1;
    char c = getchar();
    x = 0;
    while (c < '0' || c > '9') 
    {
        if (c == '-') f = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9') 
    {
        x = (x << 3) + (x << 1) + c - '0';
        c = getchar();
    }
    x *= f;
}
I void fw(LL x)
{
    if(x<0) putchar('-'),x=-x;
    static LL stak[35];
    LL top=0;
    do
    {
        stak[top++]=x%10;
        x/=10;
    }
    while(x);
    while(top) putchar(stak[--top]+'0');
    putchar('\n');
}

变量 & 数组

我们这里先用洛谷P3374 树状数组 1来举例子,因此我们需要声明的变量就有:

LL tree[500005],a[500005],n,m,l,r,kind;
//即分别是树状数组,原序列,序列长度,询问次数,每次操作的区间左右端点和操作种类。

Build

为了实现上述的种种功能,首先我们需要先把这玩意 build 出来!

I void UPD (LL x,LL k)
{
    while(x<=n) tree[x]+=k,x+=LOWBIT(x);
}
I void BUD()
{
    for(R LL i=1;i<=n;++i) UPD(i,a[i]);
}

这种方法在一般的情况下是足够用的了,不过貌似有更优的方法,但是我没学略略略。

询问

询问的是树状数组维护的前缀和。

I LL Query(LL x)
{
    LL sum=0;
    while(x) sum+=tree[x],x-=LOWBIT(x);
    Heriko sum;
}

全部 Code

上面几步基本就把这个树状数组最基本的功能实现了(

所以下面是全部的代码!(包含缺省源)

#include <bits/stdc++.h>
#define Heriko return
#define Deltana 0
#define Romano 1
#define S signed
#define U unsigned
#define LL long long
#define R register
#define I inline
#define D double
#define LD long double
#define LOWBIT(x) ((x)&(-x))
#define mst(a, b) memset(a, b, sizeof(a))
#define ON std::ios::sync_with_stdio(false)
using namespace std;
I void fr(LL & x)
{
    LL f = 1;
    char c = getchar();
    x = 0;
    while (c < '0' || c > '9') 
    {
        if (c == '-') f = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9') 
    {
        x = (x << 3) + (x << 1) + c - '0';
        c = getchar();
    }
    x *= f;
}
I void fw(LL x)
{
    if(x<0) putchar('-'),x=-x;
    static LL stak[35];
    LL top=0;
    do
    {
        stak[top++]=x%10;
        x/=10;
    }
    while(x);
    while(top) putchar(stak[--top]+'0');
    putchar('\n');
}
LL tree[500005],a[500005],n,m,l,r,kind;
I void UPD (LL x,LL k)
{
    while(x<=n) tree[x]+=k,x+=LOWBIT(x);
}
I void BUD()
{
    for(R LL i=1;i<=n;++i) UPD(i,a[i]);
}
I LL Query(LL x)
{
    LL sum=0;
    while(x) sum+=tree[x],x-=LOWBIT(x);
    Heriko sum;
}
S main()
{
    fr(n),fr(m);
    for(R LL i=1;i<=n;++i) fr(a[i]);
    BUD();
    for(R LL i=1;i<=m;++i) 
    {
        fr(kind),fr(l),fr(r);
        if(kind==1) UPD(l,r);
        else fw(Query(r)-Query(l-1));
    }
    Heriko Deltana;
}

Pro

前面说到树状数组也是能区间修改的,于是我就在这里稍微放一下我过洛谷P3368 树状数组 2的代码。

#include <bits/stdc++.h>
#define Heriko return
#define Deltana 0
#define Romano 1
#define lowbit(x) ((x)&(-x))
#define S signed
#define U unsigned
#define LL long long
#define R register
#define I inline
#define D double
#define LD long double
#define CI const int 
#define mst(a, b) memset(a, b, sizeof(a))
#define ON std::ios::sync_with_stdio(false)
using namespace std;
I void fr(LL & x)
{
    LL f = 1;
    char c = getchar();
    x = 0;
    while (c < '0' || c > '9') 
    {
        if (c == '-') f = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9') 
    {
        x = (x << 3) + (x << 1) + c - '0';
        c = getchar();
    }
    x *= f;
}
I void fw(LL x)
{
    if(x<0) putchar('-'),x=-x;
    static LL stak[35];
    LL top=0;
    do
    {
        stak[top++]=x%10;
        x/=10;
    }
    while(x);
    while(top) putchar(stak[--top]+'0');
    putchar('\n');
}
CI MXX=5e5+5;
LL tree[MXX],n,m;
I void add(LL x,LL y)
{
    while(x<=n) tree[x]+=y,x+=lowbit(x);
}
I LL query(LL x)
{
    LL ans=0;
    while(x) ans+=tree[x],x-=lowbit(x);
    Heriko ans;
}
S main()
{
    fr(n),fr(m);
    LL x=0,y,z;
    for(R LL i=1;i<=n;++i) fr(y),add(i,y-x),x=y;
    while(m--)
    {
        fr(z);
        if(z==1)
        {
            fr(x),fr(y),fr(z);
            add(x,z);
            add(y+1,-z);
            z=0;
        }
        else if(z==2)
        {
            fr(x);
            fw(query(x));
        }
    }
    Heriko Deltana;
}

End

水~淼~完了

posted @ 2021-06-12 11:24  HerikoDeltana  阅读(51)  评论(1编辑  收藏  举报