• 洛谷P3374 【模板】树状数组 1&&P3368 【模板】树状数组 2题解


    图片来自度娘~~

    树状数组形如上图,是一种快速查找区间和,快速修改的一种数据结构,一个查询和修改复杂度都为log(n),树状数组1和树状数组2都是板子题,在这里进行详解;

    求和:

    首先我们看一看这个图’

    A数组对应各个元素的值,c数组用来求和和修改。

    有连线代表着此节点的值为连线下全部子节点的和such as   c[4]=c[2]+c[3]+A[4]=A[1]+A[2]+A[3]+A[4];

    貌似没有什么神仙规律。。。。。。小学找规律题都不会了嘤嘤嘤

    那么我们看一下:

    C1 = A1                   对应的:1=2^0
    C2 = A1 + A2                         2=2^1
    C3 = A3                       3=2^1+2^0
    C4 = A1 + A2 + A3 + A4                4=2^2
     
    C5 = A5                       5=2^2+2^0
    C6 = A5 + A6                    6=2^2+2^1
    C7 = A7                       7=2^2+2^1+2^0
    C8 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8       8=2^3
    那么我们按照右边的拆分,如果要询问前7个元素(前7个A的和)的和,那么我们可以把7分成如上的3个分段(区间),分别预处理这3个区间的和,把时间复杂度降到log级别的而不是分别查找7个元素并累加7次。
    那么我们找到了降低时间复杂度的方法了,但是怎么实现?
    换句话说怎么把一个数拆成这种区间呢?
    这里我们用一个神奇的东西叫做lowbit(x), 用来一位一位的把x拆分成以上这种形式。
    我们发现,以上的形式就是一个数的二进制分解!
    那么我们在将一个任意自然数表示成二进制的时候,只要每次获取这个数二进制表示的最后为值为1的一位,并每次减去它,直到这个数为0为止才算拆分完
    看着很懵?QWQ
    我们还是要拿7举个栗子:
     
     7=2^2+2^1+2^0,也就是7用二进制表示为111;
    那么我们获取当前数二进制表示的最后为值为1的一位以及它后面所有的0构成的数,也就是最后的1,表示长度为1的区间,获取完毕后,我们减去c[7];现在数变为110,和ans加上c[7]
    我们获取当前数二进制表示的最后为值为1的一位以及它后面所有的0构成的数,也就是第二位的1,表示长度为2的区间,获取完毕后,我们减去c[7-1]也就是c[6];现在数变为100,和ans加上c[6]
    我们获取当前数二进制表示的最后为值为1的一位以及它后面所有的0构成的数,也就是开头的1,表示长度为4的区间,获取完毕后,我们减去c[6-2]也就是c[4];现在数为0,和ans加上c[4]
    那么,我们成功把前7个数的和分解为c[7],c[7-2^0]和c[7-2^0-2^1]三个区间,对照上图,我们发现ans成功表示了前7个数(A)的和。你看一下就知道了嘛。。。QWQ
     
    lowbit(x)公式就是x&(-x),
    这是啥
    1.我们对原数先取反,(就是在二进制表示下0变1,1变0,7(111)取反为000)
    2.然后加一(000+1=001)
    3.然后进行&运算(对于当前二进制数位,如果都相同(同为1或0),就返回1,else就为0)(111&001=1)
    那么lowbit(7)就位1,即从右往左数数到第一个非零位的数和它后面所有的0构成的数。
    概念算是讲清了,那么公式也讲一下:
    对于第一步:x=~x
    第二步:~x+1也就是-x,具体为什么要看电脑存储原理二进制补码,来源度娘。
    第三步,与运算:x&(~x+1)也就是x&-x
    至此,求和方法讲解完毕;
    求和函数代码:
    int query(int x){
        int ans=0;
        while(x!=0){
            ans+=tree[x];
            x-=lowbit(x);
        }
        return ans;
    }
    修改:
    对于修改操作,只要查把后面元素和当前项有关的都加上修改的值就OK了,换句话说就是只要当前项能够影响到的后面的项,就都修改。
    也就是把-lowbit(x)换成+lowbit(x)其余没大区别
    代码:
    void update(int x,int k){
        while(x<=n){//上界
            tree[x]+=k;
            x+=lowbit(x);
        }
    }

    然后,树状数组1差不多讲完了。。

    树状数组1总代码:

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    using namespace std;
    
    const int maxn=500500;
    
    int n,m;
    int tree[maxn<<2];
    
    int lowbit(int k){
        return k&(-k);
    }
    
    void update(int x,int k){
        while(x<=n){
            tree[x]+=k;
            x+=lowbit(x);
        }
    }
    
    int query(int x){
        int ans=0;
        while(x!=0){
            ans+=tree[x];
            x-=lowbit(x);
        }
        return ans;
    }
    
    int main(){
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++){
            int a;
            scanf("%d",&a);
            update(i,a);
        }
        for(int i=1;i<=m;i++){
            int a,b,c;
            scanf("%d%d%d",&a,&b,&c);
            if(a==1)update(b,c);
            else printf("%d
    ",query(c)-query(b-1));
        }
    }

    接下来是树状数组2

    有些不同。

     因为树状数组2变成了区间修改,单点询问,而区间修改如果用原来的方法会导致严重TLE。
    那么这里我们就要用差分的方法来做这道题。
    cha[i]表示a[i]-a[i-1]的值,特别的,cha[1]=a[1],因为我们设a[0]=0,那么我们,每一个数的值就可以用这个数的前缀和来表示。而这符合树状数组的求和方式。
    对于区间修改,你只要修改两个值:
    update(a,k);update(b+1,-k);
    也就是把差分数组两边的值修改一下,区间的值就可以整体变化了。
    代码如下:
    #include<iostream>
    #include<cstdio>
    using namespace std;
    int read()
    {
        int ans=0;
        char last=' ',ch=getchar();
        while(ch<'0'||ch>'9')
        {
            last=ch,ch=getchar();
        }
        while(ch>='0'&&ch<='9')
        {
            ans=(ans<<3)+(ans<<1)+ch-'0';
            ch=getchar();
        }
        return last=='-'?-ans:ans;
    }
    int n,m,c[500001],before=0,now,judge,a,b,k;
    int lowbit(int x)
    {
        return x&(-x);
    }
    void update(int x,int y)
    {
        for(;x<=n;x+=lowbit(x))c[x]+=y;
    }
    int sum(int x)
    {
        int ans=0;
        for(;x;x-=lowbit(x))ans+=c[x];
        return ans;
    }
    int main(){
        n=read();m=read();
        for(int i=1;i<=n;i++)
        {
            now=read();
            update(i,now-before);//存入差分数组而不是原数组
            before=now;
        }
        for(int i=1;i<=m;i++)
        {
            judge=read();
            if(judge==1)
            {
                a=read(),b=read();k=read();
                update(a,k);update(b+1,-k);//不同的操作
            }
            else
            {
                a=read();
                printf("%d
    ",sum(a));
            }
        }
        return 0;
    }

     完结撒花!

  • 相关阅读:
    微信小程序 页面跳转navigator与传递参数
    微信小程序 wxml中的属性记录
    iOS 集成极光推送
    ios 后台发送邮件之SKPSMTPMessage的使用
    git 配置忽略文件(忽略UserInterfaceState.xcuserstate,Breakpoints_v2.xcbkptlist)
    iOS 定位功能的实现
    ios 关于状态栏的一些小知识
    ios label上显示特殊字符 % "
    ios 按钮点击无反应
    ios 正则表达式之验证手机号、邮箱、身份证、银行卡
  • 原文地址:https://www.cnblogs.com/lbssxz/p/11130531.html
Copyright © 2020-2023  润新知