• 浅谈树状数组


    P.S. 树状数组之前认为难以理解,但是看了这个之后,恍然大悟,以下题目来自洛谷

    先三连+%up为敬

    问题P3374:给你n个数,要进行k次单点修改和区间查询的操作

    给出一个表来对比一下暴力和树状数组:

    做法 修改复杂度 查询复杂度
    朴素暴力 (O(1)) (O(n×k))
    树状数组 (O(logn)) (O(logn))

    借百度的图讲一下:

    令这棵树的结点编号为(f_1)(f_2)...(f_n),数组编号为(a_1)(a_2)...(a_n)

    所以:

    (f_1)=(a_1)

    (f_2)=(a_1)+(a_2)

    (f_3)=(a_3)

    (f_4)=(a_1)+(a_2)+(a_3)+(a_4)

    ……

    发现性质:设节点编号为(i),那么这个节点管辖的区间为(2^k)(其中(k)(i)二进制末尾0的个数)个元素

    因为这个区间最后一个元素必然为(a_i)

    比如说,我们要求(f_4),可以得到

    (f_4)=(a_1)+(a_2)+(a_3)

    把4转换为2进制,((4)_{10})=((100)_2)


    然后就是区间查询

    假如我们要查询(a_1)+(a_2)+...+(a_{13})的值就是要查询这几个区间的和的:

    所以(a_1)+(a_2)+...+(a_{13})=(f_8)+(f_{12})+(f_{13}),只用把3个值加起来就行了

    ((13)_{10})=((1101)_2)

    操作 1 2 3
    抹去二进制的最后一个1 ((1101)_2) ((1100)_2) ((1000)_2)
    转化成十进制 13 12 8
    管辖的范围 (2^0) (2^2) (2^3)

    神奇的发现:(2^0)+(2^2)+(2^3)恰好等于13

    由于每次最多抹掉(log(n))个0,也就是最多有(log(n))次查询,复杂度为(O(logn))


    接下来是单点修改

    假如要修改的节点编号为(i)

    由于(f)数组储存的是前缀和,所以要修改所有包含(i)的区间

    (i)=5时,就是要修改这几个区间的值:

    不难发现,对于单点修改的操作,其实就是把区间查询的操作倒了过来

    操作 1 2 3 4
    在二进制的最后一个1的位置加1 ((101)_2) ((110)_2) ((1000)_2) ((10000)_2)
    转化成十进制 5 6 8 16
    管辖的范围 (2^0) (2^1) (2^3) (2^4)

    这里就不再阐述了

    总结:查询就是在最后一个1的位置减1,修改就是在最后一个1的位置加1


    那怎么得到二进制的最后一个1在哪里

    (largeoxed{lowbit})操作

    inline int lowbit(int x) { return x & -x; }
    

    这段代码是什么含义呢?

    先谈谈二进制中如何表示负数:

    一个正数的相反数用二进制表示就是这个数按位取反再+1

    比如说((1100)_2)的负数用二进制表示就是((0011)_2)+1,也就是((0100)_2)

    (1100)&(0100)=(0100)

    那为什么(lowbit)能求二进制最末位的1在哪?

    我们要求的这个数末尾连续的0取反之后会全部变成1(就是二进制下这个数的相反数减一之后末尾的1与这个数末尾的0的数量相同)

    在负数中把减去的1加上之后,末尾的1会全部变成0,而最末尾的0会变成1(加法)

    以12为例子,给张图理解一下:


    放上核心代码:

    int n, f[100003];
    inline int lowbit(int x) { return x & -x; }
    inline int query(int x)//查询
    {
        int ans = 0;
        while (x)
            ans += f[x], x -= lowbit(x);
        return ans;
    }
    inline void add(int x, int val)//修改
    {
        while (x <= n)
            f[x] += val, x += lowbit(x);
    }
    

    P.S.求([l,r])区间和就是求(query(r)-query(l-1))的值


    问题P3368区间修改和单点查询

    如果上面宁已经看懂了,接下来的内容就很简单了

    区间修改,其实用到了差分的思想

    之前的(f_i)是指(1)~(i)的和(及前缀和),这里(f)储存的是差分数组

    还是拿百度的图举例子

    假如此时我们要把区间(a[1,6])都加上值(val)应该如何操作呢?

    利用差分的思想,在1的位置上加上val,在6+1的位置上减去val

    这样查询(f[1,6])时,就正好加上了值,查询到大于6的时候,先加了(val),又减去了(val)正好抵消(qwq)

    然后给出(color{limegreen}{AC})代码:

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    ll n,m,opt,l,r,k,pre,now,f[500005];
    inline ll read(){
        ll x=0,f=1;char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}
        while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
        return x*f;
    }
    inline ll lowbit(ll x){return x&-x;}
    inline ll query(ll x){ll ans=0;while(x) ans+=f[x],x-=lowbit(x);return ans;}
    inline void add(ll x,ll val){while(x<=n) f[x]+=val,x+=lowbit(x);}
    int main(){
        n=read(),m=read();
        for(ll i=1;i<=n;++i){now=read();add(i,now-pre);pre=now;}
        while(m--){
            opt=read();
            if(opt==1) {l=read(),r=read(),k=read();add(l,k),add(r+1,-k);}
            else k=read(),printf("%lld
    ",query(k));
        }
        return 0;
    }
    

    欢迎提出建议(qwq)

    完结撒花(qwq)

  • 相关阅读:
    lilntcode-508-摆动排序
    lintcode-501-迷你推特
    lintcode-496-玩具工厂
    lintcode-491-回文数
    lintcode-488-快乐数
    lintcode-480-二叉树的所有路径
    lintcode-248-统计比给定整数小的数的个数
    ubuntu 镜像站部署
    [转发]以我的亲身经历为例,告诉大家写简历和面试的技巧(面向高级开发和架构师)
    镜像站nginx
  • 原文地址:https://www.cnblogs.com/RadestionAdtinium/p/13257479.html
Copyright © 2020-2023  润新知