• C++树状数组详解


    引入

    如果给你n个数,然后进行q次询问,每次询问一个区间[x,y]的和,你会怎么做?
    第一种方法:最简单的方法,用数组存起来,每次枚举x-y,ans加起来就可以,时间复杂度O(qn),十分慢。
    第二种方法:或许大多数人会使用前缀和数组:sum[i]=a[1]+a[2]+…+a[i],所以求[x,y]只需要输出sum[y]-sum[x-1]即可,时间复杂度O(n),这是最快的方法之一了。


    但是,如果加上一个条件:在q次询问中,有可能会临时使a[m]加上或减去一个数k(我们令这个为update(m,k)操作),也有可能会查询一个区间的和,怎么办呢?
    如果还是用前缀和数组,就不方便了,因为update(m,k)需要更新sum[m]到sum[n]的值,于是时间复杂度又变为了O(qn)。
    那么怎么办呢?于是有了树状数组。

    树状数组

    概念

    树状数组,时间复杂度log级别的数据结构,且实现复杂度极小,不论是上面提到的update操作还是求前缀和。
    这里写图片描述
    如图,A数组是原始n个数的数组,C数组就是是树状数组(“树状”数组,是指一个普通数组,按树状存储,而不是一种STL中的数据结构)。

    实现

    观察一下有什么规律。

    • C[1] = A[1]
    • C[2] = C[1] + A[2] = A[1] + A[2]
    • C[3] = A[3]
    • C[4] = C[2] + C[3] +A[4] = A[1] + A[2] + A[3] + A[4]
    • C[5] = A[5]
    • C[6] = C[5] + A[6] = A[5] + A[6]
    • C[7] = A[7]
    • C[8] = C[4] + C[6] + C[7] + A[8] = A[1] + A[2] + A[3] + A[4] + A[5] + A[6] + A[7] + A[8]

    不难发现,好像和二进制很有关系。

    但是很难再想下去,事实上是这样的:
    定义lowbit(x)为x二进制下末尾0的个数。
    则含有C[i0]的C数组中的位置有:
    i0
    i1 = i0 + lowbit(i0)
    i2 = i1 + lowbit(i1)
    i3 = i2 + lowbit(i2)
    … …
    ik = ik1 + lowbit(ik1)
    ikn
    如果没法理解,写一个循环就懂了:

    for(int i=x;i<=n;i+=lowbit(i))

    计算lowbit

    lowbit(x)=x&-x
    为什么?这里复制了一篇证明(懒得打)

    首先明白一个概念,计算机中-i=(i的取反+1),也就是i的补码
    而lowbit,就是求(树状数组中)一个数二进制的1的最低位,例如01100110,lowbit=00000010;再例如01100000,lowbit=00100000。
    所以若一个数(先考虑四位)的二进制为abcd,那么其取反为(1-a)(1-b)(1-c)(1-d),那么其补码为(1-a)(1-b)(1-c)(2-d)。
    如果d为1,什么事都没有-_-|||但我们知道如果d为0,天理不容2Σ( ° △ °|||)︴
    于是就要进位。如果c也为0,那么1-b又要加1,然后又有可能是1-a……直到碰见一个为补码为0的bit,我们假设这个bit的位置为x
    这个时候可以发现:是不是x之前的bit的补码都与其自身不同?,x之后的补码与其自身一样都是0?
    例如01101000,反码为10010111,补码为10011000,可以看到在原来数正数第五位前,补码的进位因第五位使其不会受到影响,于是0&1=0,;
    但在这个原来数“1”后,所有零的补码都会因加1而进位,导致在这个“1”后所有数都变成0,再加上0&0=0,所以他们运算结果也都是零;
    只有在这个数处,0+1=1,连锁反应停止,所以这个数就被确定啦O(∩_∩)O
    所以and以后只有x这个bit是一……

    update操作

    当要动态改变一个数时,用刚刚的循环枚举出与它相关的位置,都增加(减少)即可:

    void update(int k,int x)
    {
        for(int i=k;i<=n;i+=lowbit(i))
            C[i]+=x;
    }

    getsum操作

    就是求前缀和,同样的,倒着进行刚刚的循环,累加路上的值即可:

    int getsum(int x)
    {
        int ans=0;
        for(int i=x;i;i-=lowbit(i))//i要大于0
            ans+=C[i];
        return ans;
    }

    关于代码风格

    树状数组的update和getsum基本是通用的,建议不要自己改函数名,lowbit可以写函数,也可以宏定义:#define lowbit(x) (x&-x)

    例题

    Stars

  • 相关阅读:
    mac os 基本命令
    一个程序员的郁闷吐槽
    域名那些事儿
    EventEmitter事件派发器
    Array类型的操作方法
    居中与垂直居中
    Web Storage —— 登录时记住密码
    字符串字符统计
    颜色字符串转换(正则)
    将字符串转换为驼峰格式
  • 原文地址:https://www.cnblogs.com/LinqiongTaoist/p/7203726.html
Copyright © 2020-2023  润新知