• 树状数组的一系列操作


    树状数组的一系列操作

    1、树状数组求逆序对

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    using namespace std;
    const int maxn=100010;
    int n,a[maxn],b[maxn],c[maxn],s[maxn];
    int lowbit(int x)
    {
        return x&(-x);
    }
    void add(int x)
    {
        for(int i=x;i<=n;i+=lowbit(i))
        c[i]++;
    }
    int getsum(int x)
    {
        int ans=0;
        for(int i=x;i>=1;i-=lowbit(i))
        ans+=c[i];
        return ans;
    }
    int main()
    {
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
        scanf("%d",&a[i]),s[i]=a[i];
        sort(a+1,a+n+1);
        int sum=unique(a+1,a+n+1)-a-1;
        for(int i=1;i<=n;i++)
        {
            int pos=lower_bound(a+1,a+sum+1,s[i])-a;
            b[i]=pos;
        }
        int ans=0;
        for(int i=n;i>=1;i--)
        {
            ans+=getsum(b[i]);
            add(b[i]+1);
        }
        cout<<ans;
        return 0;
    }

    2、树状数组区间修改
    这里由于涉及到区间修改,所以我们要引入差分的思想。
    差分数组c[]:我们设sigma(c,i)表示c数组的前i项和,调用一次的复杂度是log2(i)
    设原数组是a[n],差分数组c[n],c[i]=a[i]-a[i-1],那么明显地a[i]=sigma(c,i),如果想要修改a[i]到a[j],只需令c[i]+=v,c[j+1]-=v
    明白了上面的原理以后,就可以直接看代码了

    /*区间修改+单点查询*/
    #include<iostream>
    #include<cstdio>
    using namespace std;
    const int maxn=500010;
    int n,m,a[maxn],c[maxn];
    int lowbit(int x)
    {
        return x&(-x);
    }
    void add(int x,int y)
    {
        for(int i=x;i<=n;i+=lowbit(i))
        c[i]+=y;
    }
    int getsum(int x)
    {
        int ans=0;
        for(int i=x;i>=1;i-=lowbit(i))
        ans+=c[i];
        return ans;
    }
    int main()
    {
        int x,y,z,k;
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            add(i,a[i]-a[i-1]);
        }
        for(int i=1;i<=m;i++)
        {
            scanf("%d",&k);
            if(k==1)
            {
                scanf("%d%d%d",&x,&y,&z);
                add(x,z);add(y+1,-z);
            }
            else
            {
                scanf("%d",&x);
                printf("%d
    ",getsum(x));
            }
        }
        return 0;
    }
    /*单点修改+区间查询*/
    #include<iostream>
    #include<cstdio>
    using namespace std;
    const int maxn=500010;
    int n,m,c[maxn];
    int lowbit(int x)
    {
        return x&(-x);
    }
    void add(int x,int y)
    {
        for(int i=x;i<=n;i+=lowbit(i))
        c[i]+=y;
    }
    int getsum(int x)
    {
        int ans=0;
        for(int i=x;i>=1;i-=lowbit(i))
        ans+=c[i];
        return ans;
    }
    int main()
    {
        int x,y,k;
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&x);
            add(i,x);
        }
        for(int i=1;i<=m;i++)
        {
            scanf("%d",&k);
            if(k==1)
            {
                scanf("%d%d",&x,&y);
                add(x,y);
            }
            else
            {
                scanf("%d%d",&x,&y);
                printf("%d
    ",getsum(y)-getsum(x-1));
            }
        }
        return 0;
    }

    注意:
    树状数组c[]始终是前缀和数组,即:getsum(i)始终得到的是点i的前缀和。

    区间修改是将树状数组变成了差分数组,所以getsum(i)得到的是差分前缀和,由上面的推导,i的差分前缀和,就是点i的值,所以getsum(i)是单点查询,即:getsum(i)==a[i]

    单点修改不需要差分,所以树状数组维护的就是x的前缀和,即getsum(x)==a[1]+a[2]+……+a[x],此时getsum()函数变成了区间查询函数
    举个例子:x~y的区间和==getsum(y)-getsum(x-1)

    拓展:
    (两个差分数组还原了前缀和数组orz~~)

    #include<iostream>
    #include<cstdio>
    using namespace std;
    const int maxn=200010;
    int n,m,s[maxn],t[maxn],r[maxn];
    int lowbit(int x)
    {
        return x&(-x);
    }
    void add1(int x,int y)
    {
        for(int i=x;i<=n;i+=lowbit(i))
        t[i]+=y;
    }
    void add2(int x,int y)
    {
        for(int i=x;i<=n;i+=lowbit(i))
        r[i]+=y;
    }
    int find1(int x)
    {
        int ans=0;
        for(int i=x;i>=1;i-=lowbit(i))
        ans+=t[i];
        return ans;
    }
    int find2(int x)
    {
        int ans=0;
        for(int i=x;i>=1;i-=lowbit(i))
        ans+=r[i];
        return ans;
    }
    int getsum(int x)
    {
        int ans=0;
        ans=s[x]+(x+1)*find1(x)-find2(x);
        return ans;
    }
    int main()
    {
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
        {
            int x;
            scanf("%d",&x);
            s[i]=s[i-1]+x;
        }
        scanf("%d",&m);
        for(int i=1;i<=m;i++)
        {
            int x,y,z,w;
            scanf("%d",&w);
            if(w==1)
            {
                scanf("%d%d%d",&x,&y,&z);
                add1(x,z);add1(y+1,-z);
                add2(x,x*z);add2(y+1,(y+1)*(-z));
            }
            else
            {
                scanf("%d%d",&x,&y);
                printf("%d
    ",getsum(y)-getsum(x-1));
            }
        }
        return 0;
    }
  • 相关阅读:
    [转]java常量池理解总结
    设计数据库时使用外键吗
    ajax 下载文件
    CentOS6.4配置163的yum源
    Xms Xmx PermSize MaxPermSize 区别
    [转]基于java的程序OutOfMemory问题的解决及Xms/Xmx/Xss的解释和应用
    Java Web Services面试
    java 单例模式
    Spring和MyBatis整合
    Spring核心概念
  • 原文地址:https://www.cnblogs.com/cax1165/p/6070872.html
Copyright © 2020-2023  润新知