• 树状数组操作总结


      网上已经有很多大佬总结得很好了,我就直接转发膜拜一发。

     入门讲解博客Small_Orange_glory大佬的讲解,单点更新,区间查询,带图挺好理解

     进阶讲解博客园cc123321大佬的讲解,区间修改,单点查询;区间修改,区间查询,写得很明朗

     总结讲解博客园胡小兔大佬的讲解,所有操作都讲了一遍,还附带了二维的树状数组(还没学,留个坑)

      耐心看完这三位大佬的博客,我想理解树状数组并不难。其实树状数组并不难,但第一次遇到时,自己心气浮躁,不能静下心去好好学习一下,学习还是得静心,戒骄戒躁。当然光看懂就是不行的,还是得会用,这里拉几题练练手。

    P3374树状数组模板一

      不得不说,整出树状数组的那个人真是神奇,虽然说有些线段树的操作。树状数组做不了,但是在都能做的操作下,树状数组不管是效率还是实现都快得多。我个人感觉树状数组是一个二进制和树结构的结合,lowbit就相当于树中每个节点相连的边,当要更新某个节点的信息时,受它影响的节点的信息也同步更新。像这个看这个简单的丑丑的上图,我们要求a数组区间1~4的和的话,也就是a1+a2+a3+a4时,不就是求c4吗,所以更新a1时同步的c2,c4也更新,a1+lowbit(a1)其实就可以看成a1和c2的连线。而当我们要求a1+a2+a3时,也就是求c2+a3,这时a3-lowbit(a3)可以看出a3和c2连的一条虚线(忘了画了,假装有的样子)。lowbit(x)的本质是什么,x最尾部1所代表的数,也就是2k(k就是尾部0的长度)。+lowbit的话从下图中体现就是,一直斜着向上到最近的节点(看1到2到4到8)(产生影响),-lowbit就是反过来的斜着向上到最近的节点(看7到6到4)(统计权值,因为像6不能由4加lowbit转移过来,所以4中的修改,6是不受4影响的,所以我们要统计前7个数的加和时就是节点7把在它之前没有对它产生影响的节点权值加上)。

    #include<cstdio>
    #define ll long long 
    #define lowbit(x) x&(-x)
    const int N=520118; 
    int n,m,op;
    long a[N]={0};
    void updata(int x,ll y)
    {
        while(x<=n)
        {
            a[x]+=y;
            x+=lowbit(x);
        }
    }
    ll getsum(int x)
    {
        ll ans=0;
        while(x)
        {
            ans+=a[x];
            x-=lowbit(x);
        }
        return ans;
    }
    int main()
    {
        ll x,y;
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
        {
            scanf("%lld",&x);
            updata(i,x);
        }
        while(m--)
        {
            scanf("%d%lld%lld",&op,&x,&y);
            if(op==1)
                updata(x,y);
            else
                printf("%lld
    ",getsum(y)-getsum(x-1));
        }
        return 0;
    }
    单点更新,区间查询

    P3368树状数组二

      我们在做单点更新区间查询时,就是把a数组当成叶子节点,然后求sum数组,所以现在单点查询的话,我们就可以把a数组当成sum数组,也就是另一个数组的前n项和,假设a[i]=a[i-1]+b[i]的话,那么a[i[不就是b[i[的前n项和了。所以我们把b[i]用树状数组表示出来,查询a[i]就是查询b[1]+b[2]+b[3]+b[4]+。。+b[i](a[i]的单点查询就是b[1]~b[i]的区间查询),然后区间修改呢,我们要想在a数组的,[l,r]这样一个区间里的每个a[i]都加x的话。我们看,a[l]=b[1]+b[2]+b[3]+..+b[l],a[l+1]=b[1]+b[2]+b[3]+..+b[l]+b[l+1],a[r]=b[1]+b[2]+b[3]+..+b[l]+b[l+1]+..+b[r],所以只要在b[l]处加上x,那么对后面的a[l],a[l+1]..a[r}都产生了加x的影响,那么怎么不对后面的产生影响呢,很简单,在b[r+1]处加上个-x就可以了(a[i]~a[j]的区间修改就是b[i]和b[j+1]的单点更新)

    #include<cstdio>
    #define lowb(x) x&(-x) 
    #define ll long long
    const int N=520118;
    ll a[N]={0},b[N]={0};
    int n,m;
    void updata(int x,ll y)
    {
        while(x<=n)
        {
            b[x]+=y;
            x+=lowb(x);
        }
    }
    void modify(int x,int y,ll z)
    {
        updata(x,z);
        updata(y+1,-z);
    }
    ll geta(int x)
    {
        ll ans=0;
        while(x)
        {
            ans+=b[x];
            x-=lowb(x);
        }
        return ans;
    }
    int main()
    {
        ll k;
        int x,y,op;
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
            scanf("%lld",&a[i]);
        while(m--)
        {
            scanf("%d%d",&op,&x);
            if(op==1)
            {
                scanf("%d%lld",&y,&k);
                modify(x,y,k);
            }
            else
                printf("%lld
    ",geta(x)+a[x]);
        }
        return 0;
    }
    区间更新,单点查询

    POJ3468一个关于整数的简单问题

      现在我们要求区间a[1]~a[i]的和,因为a[i]是b[i]的前n项和,那么a[1]+..+a[i]=b[1]+b[1]+b[2]+b[1]+b[2]+b[3]+...+b[1]+..+b[i]=i*b[1]+(i-1)*b[2]+(i-2)*b[3]+...+b[i],那怎么用树状数组表示出来呢,我们把公式转换一下就是,k*(b[1]+b[2]+b[3]+..b[i])-(0*b[1]+1*b[2]+2*b[3]+..+(i-1)*b[i])),b[1]+b[2]+b[3]+..+b[i]不难求吧,就是b[1]~b[i]的区间查询,然后后面部分呢,我们让c[i]=(i-1)*b[i],这时候把c[i]用树状数组表示出来,后面部分就是c[1]~c[i]的区间查询。可能会对c[i]的更新有疑惑,这里说一下,加入b[i]一开始是y,那么c[i[就是(i-1)*y,这时b[i]加上x,那么c[i]也得更新,也就是c[i]加上(i-1)*x,然后b[i]变成了x+y,c[i]变成了(i-1)(x+y),完美同步更新

     1 #include<cstdio>
     2 #define ll long long
     3 #define lowb(x) x&(-x)
     4 const int N=100118;
     5 char op[3];
     6 ll n,m,a[N]={0},b[N]={0},c[N]={0};
     7 void updata(ll q[],ll x,ll y)
     8 {
     9     while(x<=n)
    10     {
    11         q[x]+=y;
    12         x+=lowb(x);
    13     }
    14 }
    15 ll geta(ll q[],ll x)
    16 {
    17     ll ans=0;
    18     while(x)
    19     {
    20         ans+=q[x];
    21         x-=lowb(x);
    22     }
    23     return ans;
    24 }
    25 void modify(ll x,ll y,ll z)
    26 {
    27     updata(b,x,z);
    28     updata(b,y+1,-z);
    29     updata(c,x,(x-1)*z);
    30     updata(c,y+1,-y*z);
    31 }
    32 ll getsum(ll x,ll y)
    33 {
    34     ll ans=0;
    35     ans+=y*geta(b,y)-geta(c,y);
    36     ans-=(x-1)*geta(b,x-1)-geta(c,x-1);
    37     return ans;
    38 }
    39 int main()
    40 {
    41     scanf("%lld%lld",&n,&m);
    42     for(int i=1;i<=n;i++)
    43     {
    44         scanf("%lld",&a[i]);
    45         updata(b,i,a[i]-a[i-1]);
    46         updata(c,i,(i-1)*(a[i]-a[i-1]));
    47     }
    48     ll x,y,z;
    49     while(m--)
    50     {
    51         scanf("%s%lld%lld",op,&x,&y);
    52         if(op[0]=='Q')
    53             printf("%lld
    ",getsum(x,y));
    54         else
    55         {
    56             scanf("%lld",&z);
    57             modify(x,y,z);
    58         }
    59     }
    60     return 0;
    61 }
    区间更新区间查询
  • 相关阅读:
    springboot项目下载文件功能中-切面-导致的下载文件失败的bug
    解决laydate动态设置初始值的问题
    简易MySQL存储过程
    css选择器(第n个类选择器)的坑
    session过期情况下ajax请求不会触发重新登录的问题
    session过期情况下ajax请求不会触发重新登录的问题
    springboot配置多数据源mongodb
    css平移动画的实现
    springboot中使用JOIN实现关联表查询
    解决springboot项目中@Value注解参数值为null的问题
  • 原文地址:https://www.cnblogs.com/LMCC1108/p/10549672.html
Copyright © 2020-2023  润新知