zoukankan      html  css  js  c++  java
  • “高级”数据结构——树状数组!

    转自  https://www.cnblogs.com/RabbitHu/p/BIT.html

    1. 单点修改 + 区间查询

    最简单的树状数组就是这样的:

    void add(int p, int x){ //给位置p增加x
        while(p <= n) sum[p] += x, p += p & -p;
    }
    int ask(int p){ //求位置p的前缀和
        int res = 0;
        while(p) res += sum[p], p -= p & -p;
        return res;
    }
    int range_ask(int l, int r){ //区间求和
        return ask(r) - ask(l - 1);
    }

    2. 区间修改 + 单点查询

    通过“差分”(就是记录数组中每个元素与前一个元素的差),可以把这个问题转化为问题1。

    查询

    设原数组为a[i], 设数组d[i]=a[i]−a[i−1](a[0]=0),则 a[i]=∑ij=1d[j],可以通过求d[i]的前缀和查询。

    修改

    当给区间[l,r]加上x的时候,a[l] 与前一个元素 a[l−1] 的差增加了x,a[r+1] 与 a[r] 的差减少了x。根据d[i]数组的定义,只需给a[l] 加上 x, 给a[r+1] 减去 x即可。

    void add(int p, int x){ //这个函数用来在树状数组中直接修改
        while(p <= n) sum[p] += x, p += p & -p;
    }
    void range_add(int l, int r, int x){ //给区间[l, r]加上x
        add(l, x), add(r + 1, -x);
    }
    int ask(int p){ //单点查询
        int res = 0;
        while(p) res += sum[p], p -= p & -p;
        return res;
    }

    3. 区间修改 + 区间查询

    这是最常用的部分,也是用线段树写着最麻烦的部分——但是现在我们有了树状数组!

    怎么求呢?我们基于问题2的“差分”思路,考虑一下如何在问题2构建的树状数组中求前缀和:

    位置p的前缀和 =∑i=1pa[i]=∑i=1p∑j=1id[j]在等式最右侧的式子∑pi=1∑ij=1d[j]中,d[1] 被用了p次,d[2]被用了p−1

    次……那么我们可以写出:位置p的前缀和 =∑i=1p∑j=1id[j]=∑i=1pd[i]∗(p−i+1)=(p+1)∗∑i=1pd[i]−∑i=1pd[i]∗i

    那么我们可以维护两个数组的前缀和:一个数组是 sum1[i]=d[i],另一个数组是 sum2[i]=d[i]∗i。

    查询

    位置p的前缀和即: (p + 1) * sum1数组中p的前缀和 - sum2数组中p的前缀和。

    区间[l, r]的和即:位置r的前缀和 - 位置l的前缀和。

    修改

    对于sum1数组的修改同问题2中对d数组的修改。

    对于sum2数组的修改也类似,我们给 sum2[l] 加上 l * x,给 sum2[r + 1] 减去 (r + 1) * x。

    void add(ll p, ll x){
        for(int i = p; i <= n; i += i & -i)
            sum1[i] += x, sum2[i] += x * p;
    }
    void range_add(ll l, ll r, ll x){
        add(l, x), add(r + 1, -x);
    }
    ll ask(ll p){
        ll res = 0;
        for(int i = p; i; i -= i & -i)
            res += (p + 1) * sum1[i] - sum2[i];
        return res;
    }
    ll range_ask(ll l, ll r){
        return ask(r) - ask(l - 1);
    }

    用这个做区间修改区间求和的题,无论是时间上还是空间上都比带lazy标记的线段树要优。

    转自另一个大佬https://blog.csdn.net/zars19/article/details/54620021

    单点修改 区间查询

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<cstdlib>
    using namespace std;
    int n,m;
    int c[500005];
    int lowbit(int x)
    {
        return (-x)&x;
    }
    void add(int pos,int x)
    {
        while(pos<=n)
        {
            c[pos]+=x;
            pos+=lowbit(pos);
        }
    }
    void input()
    {
        int x;
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&x);
            add(i,x);
        }
    }
    int query(int pos)
    {
        int res=0;
        while(pos>0)
        {
            res+=c[pos];
            pos-=lowbit(pos);
        }
        return res;
    }
    int main()
    {
        scanf("%d%d",&n,&m);
        input();
        int f,x,y;
        for(int i=1;i<=m;i++)
        {
            scanf("%d%d%d",&f,&x,&y);
            if(f==1)
            add(x,y);
            else if(f==2)
            cout<<query(y)-query(x-1)<<endl;
        }
        return 0;
    }

    区间修改 单点查询

    #include<iostream>
    #include<cstring>
    #include<queue>
    #include<cstdio>
    using namespace std;
    int c[500005];
    int n,m;
    int lowbit(int x)
    {
        return x&(-x);
    }
    void add(int pos,int x)
    {
        while(pos<=n)
        {
            c[pos]+=x;
            pos+=lowbit(pos);
        }
    }
    int query(int pos)
    {
        int res=0;
        while(pos>0)
        {
            res+=c[pos];
            pos-=lowbit(pos);
        }
        return res;
     } 
    int main()
    {
        scanf("%d%d",&n,&m);
        int x=0,y;
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&y);
            add(i,y-x);
            x=y;
        }
        int opt,k;
        for(int i=1;i<=m;i++)
        {
            scanf("%d",&opt);
            if(opt==1)
            {
                scanf("%d%d%d",&x,&y,&k);
                add(x,k);
                add(y+1,-k);
            }
            else if(opt==2)
            {
                scanf("%d",&x);
                printf("%d
    ",query(x));
            }
        }
        return 0;
     } 

    区间修改 区间查询*

    此处简略地说明一下原数组a,差分数组d 则有an=∑i=1ndi所以∑i=1nai=∑i=1n∑j=1idj=∑i=1n(n−i+1)×di=(n+1)×∑i=1ndi−∑i=1ndi×i

    于是我们维护两个树状数组,c1储存di,c2储存di×i

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    using namespace std;
    long long c[200005][2];
    int n,q;
    int lowbit(int x)
    {
        return x&(-x);
    }
    void add(int pos,int x,int f)
    {
        while(pos<=n)
        {
            c[pos][f]+=x;
            pos+=lowbit(pos);
        }
    }
    long long query(int pos,int f)
    {
        long long res=0;
        while(pos>0)
        {
            res+=c[pos][f];
            pos-=lowbit(pos);
        }
        return res;
     }
    long long ask(int pos)
    {
        long long res=(pos+1)*query(pos,0)-query(pos,1);
        return res; 
    }
    int main()
    {
        scanf("%d",&n);
        int a=0,b,opt,x;
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&b);
            add(i,b-a,0);
            add(i,(b-a)*i,1);
            a=b;
        }
        scanf("%d",&q);
        for(int i=1;i<=q;i++)
        {
            scanf("%d",&opt);
            if(opt==1)
            {
                scanf("%d%d%d",&a,&b,&x);
                add(a,x,0);
                add(b+1,-x,0);
                add(a,x*a,1);
                add(b+1,-x*(b+1),1);
            }
            else if(opt==2)
            {
                scanf("%d%d",&a,&b);
                printf("%lld
    ",ask(b)-ask(a-1));
            }
        }
        return 0;
    } 
  • 相关阅读:
    POJ-2112 Optimal Milking(floyd+最大流+二分)
    网络流之最大流与最小费用流入门&&模板
    0316 校赛训练赛3 解题报告
    string的子串截取
    hash题目大汇总
    Codeforces Round #235 (Div. 2)
    poj2002 -- 点的hash
    hlgHCPC2014校赛训练赛 1 BB.序列问题
    树状数组模板,RMQ模板
    从未放弃--2014.1.21
  • 原文地址:https://www.cnblogs.com/zcy19990813/p/9702723.html
Copyright © 2011-2022 走看看