zoukankan      html  css  js  c++  java
  • 树状数组萌新讲解+基础习题【一点一滴】

    树状数组基础篇

    树状数组讲点

    中文名:树状数组
    英文名:Binary Indexeds Tree
    英译中:二进制索引树
    这特么多清楚

    引入:
    给你n个数
    1. 求区间的的和
    2. 改变某个值

    然后朴素做法肯定GG,这里就有了树状数组的神奇功效。

    如果在时间空间允许的朴素做法也行啊,树状数组就是这么牛逼,他就是可以用它独有的特性而做好这件事情。直接开门见山吧。

    首先得搞清楚树状数组的x(下标)运算:x&(-x);
    当x是奇数的时候,最后一个比特是1,然后负数是取反+1,所以x&(-x),前面全部是0,最后是一,得出奇数的答案都是1.
    当x为偶数的时候,①如果他的形式就是2^m,那么他的结果就是x;②如果不是,都是自己模拟的,难说,自己搞吧。答案就是:如果x二进制最右边的1,右边有k个0,答案就是2^k.
    我们把A数组作为本来存储每个元素的数组;
    从A数组转变成C数组(树状数组);联系那个下标运算
    c1=a1,c2=a1+a2,c3=a3,c4=a1+a2+a3+a4,c5=a5,c6=a5+a6,c7=a7,c8=a1+a2+a3+a4+a5+a6+a7+a8,c9=a9,c10=a9+a10,c11=a11……..c16=a1+a2+a3+a4+a5+…….+a16。 当 i 为奇数时,ci=ai ;当 i 为偶数时,就要看 i 的因子中最多有二的多少次幂,例如,6 的因子中有 2 的一次幂,等于 2 ,所以 c6=a5+a6(由六向前数两个数的和),4 的因子中有 2 的两次幂,等于 4 ,所以 c4=a1+a2+a3+a4(由四向前数四个数的和)。

    C数组C[i] = a[i – 2^k + 1] + … + a[i] k为二进制下某尾0的个数。

    Lowbit(): 返回的就是 2^k的值。

    int lowbit(int t)
    {
        return t&(-t);
    }

    Add(): 更新树状数组;

    void Add(int i,int t)
    {
        while(i<=n)
        {
            c[i]+=t;
            i+=lowbit(i);
        }
    }

    int sum() 就是把C数组全部加起来就好了,会得到一个下标为i前缀和

    int Sum(int i)
    {
        int sum=0;
        while(i)
        {
            sum+=c[i];
            i-=lowbit(i);
        }
        return sum;
    }

    Change() 当有元素变更的时候,树状数组的优势就特别大。

    void Change(int i,int num)
    {
        //这个往往为变成数据范围+7,反正超出就行,因为你改变一个值以后,后面的有些c数组会改变,为什么说有些呢,结合c数组的特性,那个运算,我们可以发现什么?留给巨巨们思考了
         while(i<=n)
    {
            c[i]+=x;
            i+=lowbit(i);
        }
    }

    一个小插曲(也可以解释前面的为什么):
    插:错误往往都是很能说明问题的,但是你首先得明确问题,才能办好事。

    要求:
    实现在一个区间都加上x,然后计算区间值。
    插:如果巨巨看出为什么错的话,那后面的解释就当看看过吧,或者再给我说说,我也好更加理解,谢谢~

    想法(错误的):
    弱的思路就是:lowbit就是他要加上的区间宽度,那么乘以要加的值再加上前面加的就是所有要加的。。。。。
    代码:

    void Add_duan(int i,LL t,int w)     //i是区间起始点,t是要加的值,w是末端
    {
        LL temp=t;
        LL q;
        while(i<=w)
        {
            q=t;
            C[i]+=t;
            t=lowbit(i)*temp+t;
            i+=lowbit(i);
        }
        while(i<=n)
        {
            C[i]+=q;
            i+=lowbit(i);
        }
    }

    然后就不行啊!!!
    我只能解释一下(似乎解释不全):
    树状数组英文名:Binary Indexeds Tree,再英译汉一下,二进制索引树。
    二进制索引的特性,当我要把2-4这个范围内的数+1的话,我跑我刚刚的程序,直接把3略过去了,3只能吃2的,3没有被处理。
    类同…4-8-16-32区间内的

    GG
    而且这个BIT为什么复杂度在log(n);也是这个道理啊,所以BIT就是只能用来计算前缀和的。。。
    OK,巨巨请多多建议一下。讲的也很挫…

    区间更新正解(引我大哥的博文):
    http://www.wonter.net/?p=335

    几道基础题

    POJ2352;

    题意:
    计算每个等级的星星有多少颗,0~n-1个等级,每个星星的等级=每个星星左下角星星的数量
    思路:
    输入本来就是给出以y为增序,即y已经保证后面的肯定比前面大于等于,那么直接处理x,每次计算x的前缀和,然后更新,用个level数组记录就好了;
    code……….

    //#include<bits/stdc++.h>
    #include<cstdio>
    #include<iostream>
    #include<math.h>
    #include<string.h>
    #include<algorithm>
    using namespace std;
    typedef long long LL;
    typedef unsigned long long ULL;
    const double eps=1e-5;
    const double pi=acos(-1.0);
    const int mod=1e8+7;
    const int INF=0x3f3f3f3f;
    
    const int N=15005;
    const int M=32005;
    
    int le[N];
    int c[M];
    int n;
    
    void add(int i,int t)
    {
        while(i<=M)
        {
            c[i]+=t;
            i+=i&(-i);
        }
    }
    int Sum(int i)
    {
        int ans=0;
        while(i>0)      //如果是0的话,进过位运算只能是0,0-0还是0,所以要避免
        {
            ans+=c[i];
            i-=i&(-i);
        }
        return ans;
    }
    
    int main()
    {
        scanf("%d",&n);
        memset(c,0,sizeof(c));
        memset(le,0,sizeof(le));
        int x,y;
        for(int i=1;i<=n;i++)
        {
            scanf("%d%d",&x,&y);
            le[Sum(x+1)]++;     //让标志点全部往右移一个,而更新的所以也要改,防止0,会陷入死循环
            add(x+1,1);
        }
        for(int i=0;i<n;i++)
        printf("%d
    ",le[i]);
        return 0;
    }
    
    

    POJ 2481;

    题意:
    计算每个区间有多少个包含他的区间。
    思路:
    树状数组只能计算前缀和,那么前缀和首先得有前缀啊。我们要计算一个区间有多少个包含他,那么所以越小的区间要越晚处理。那么我们按照y从大到小排序,x从小到大,然后就像慢慢逼近小区间一样,计算x的前缀和就好了。
    code…………(C++过,G++ t掉了…)

    //#include<bits/stdc++.h>
    #include<cstdio>
    #include<iostream>
    #include<math.h>
    #include<string.h>
    #include<algorithm>
    using namespace std;
    typedef long long LL;
    typedef unsigned long long ULL;
    const double eps=1e-5;
    const double pi=acos(-1.0);
    const int mod=1e8+7;
    const int INF=0x3f3f3f3f;
    
    const int N=1e5+10;
    
    int c[N];
    int cnt[N];
    struct asd{
        int s,e;
        int pos;
    };
    asd q[N];
    int n;
    bool cmp(asd x,asd y)
    {
        if(x.e==y.e)
            return x.s<y.s;
        return x.e>y.e;
    }
    int lowbit(int i)
    {
        return i&(-i);
    }
    int SUM(int i)
    {
        int s=0;
        while(i>0)
        {
            s+=c[i];
            i-=lowbit(i);
        }
        return s;
    }
    void add(int i,int t)
    {
        while(i<=n)
        {
            c[i]+=t;
            i+=lowbit(i);
        }
    }
    
    int main()
    {
        while(scanf("%d",&n)&&n)
        {
            for(int i=1;i<=n;i++)
            {
                scanf("%d%d",&q[i].s,&q[i].e);
                q[i].pos=i;
            }
            sort(q+1,q+n+1,cmp);
    
            memset(cnt,0,sizeof(cnt));
            memset(c,0,sizeof(c));
            cnt[q[1].pos]=0;
            add(q[1].s+1,1);
            for(int i=2;i<=n;i++)
            {
                if(q[i].s==q[i-1].s&&q[i].e==q[i-1].e)
                    cnt[q[i].pos]=cnt[q[i-1].pos];
                else
                    cnt[q[i].pos]=SUM(q[i].s+1);
                add(q[i].s+1,1);
            }
            for(int i=1;i<=n;i++)
            {
                if(i!=1)
                    printf(" ");
                printf("%d",cnt[i]);
            }
            puts("");
        }
        return 0;
    }

    POJ1990

    题意:
    计算所有两两点的距离*max(v[i],v[j]);
    思路:
    我们还是先按照v值按照降序排序,那么对于一个点,这个v是确定的。这题计算距离,利用前缀和。我们可以发现,一个点左边右边都有可能有点,我们计算距离=xj(右边)-x或者=x-xj(左边);那么我们对左边的点累加起来就是=n1*x-(sum)xj;n1为左边有多少个点,(sum)xj为左边的点的坐标之和。右边同理可得。那么刚好,这个左边右边的点数利用树状数组可以实现,不就是个前缀和,最多加加减减。距离也是。
    code………………

    //#include<bits/stdc++.h>
    #include<cstdio>
    #include<iostream>
    #include<math.h>
    #include<string.h>
    #include<algorithm>
    using namespace std;
    typedef long long LL;
    typedef unsigned long long ULL;
    const double eps=1e-5;
    const double pi=acos(-1.0);
    const int mod=1e8+7;
    const int INF=0x3f3f3f3f;
    
    const int N=20025;
    LL dis[N];
    LL num[N];
    struct asd{
        LL v;
        LL x;
    };
    int n;
    asd q[N];
    bool cmp(asd z1,asd z2)
    {
        return z1.v<z2.v;
    }
    
    LL Sum(LL i,LL *x)
    {
        LL ans=0;
        while(i>0)
        {
            ans+=x[i];
            i-=i&(-i);
        }
        return ans;
    }
    void add(LL i,LL t,LL *x)
    {
        while(i<=N-10)
        {
            x[i]+=t;
            i+=i&(-i);
        }
    }
    int main()
    {
        scanf("%d",&n);
        for(int i=0;i<n;i++)
            scanf("%lld%lld",&q[i].v,&q[i].x);
        sort(q,q+n,cmp);
        memset(num,0,sizeof(num));
        memset(dis,0,sizeof(dis));
        LL ans=0;
        for(int i=0;i<n;i++)
        {
            LL x=q[i].x;
            LL le=Sum(x-1,num);
            LL ri=Sum(N-10,num)-Sum(x,num);
            ans+=q[i].v*(le*x-Sum(x-1,dis)+Sum(N-10,dis)-Sum(x,dis)-ri*x);
            add(x,1,num);
            add(x,x,dis);
        }
        printf("%lld
    ",ans);
        return 0;
    }
    

    POJ2299

    题意:
    求一个序列的逆序数。
    思路:
    这个网上讲的挺好的。多了一个离散化操作。还有就是树状数组就是可以计算一个前缀和。逆序数=位置-前面比他小的;(话说我们应该更好理解就是后面有多少比他大的吧,其实一样,都是前缀和可以解决,具体看别的博文吧,弱不想说这个了。。。)
    code…………

    //#include<bits/stdc++.h>
    #include<cstdio>
    #include<iostream>
    #include<math.h>
    #include<string.h>
    #include<algorithm>
    using namespace std;
    typedef long long LL;
    typedef unsigned long long ULL;
    const double eps=1e-5;
    const double pi=acos(-1.0);
    const int mod=1e8+7;
    const int INF=0x3f3f3f3f;
    
    /*
    
    利用的就是树状数组的单点更新和区间求值,但是逆序嘛,也是可以求后面有多少比他大的,
    枚举数组就可以依次将值塞进数组,然后瞎搞一下就好了。
    逆序:
    其中 i 为当前已经插入的数的个数,
    getsum( aa[i] )为比 aa[i] 小的数的个数,
    i- sum( aa[i] ) 即比 aa[i] 大的个数, 即逆序的个数
    
    */
    
    const int N=1e5+7;
    struct asd{
        int v,pos;
    };
    asd q[N*5];
    int ans[N*5];
    int c[N*5];
    int n;
    
    bool cmp(asd x,asd y)
    {
        return x.v<y.v;
    }
    int getsum(int i)
    {
        int sum=0;
        while(i>=1)
        {
            sum+=c[i];
            i-=i&(-i);
        }
        return sum;
    }
    
    void update(int i,int t)
    {
        while(i<=n)
        {
            c[i]+=t;
            i+=i&(-i);
        }
    }
    
    int main()
    {
        while(~scanf("%d",&n)&&n)
        {
            memset(ans,0,sizeof(ans));
            for(int i=1;i<=n;i++)
            {
                scanf("%d",&q[i].v);
                q[i].pos=i;
            }
            sort(q+1,q+n+1,cmp);
            for(int i=1;i<=n;i++)
                ans[q[i].pos]=i;
    
    
            memset(c,0,sizeof(c));
    
            LL sum=0;
            for(int i=1;i<=n;i++)
            {
                update(ans[i],1);
                sum+=i-getsum(ans[i]);
            }
            printf("%lld
    ",sum);
        }
        return 0;
    }
    
    

    POJ3067

    直接code……..如果刷完了上面,这题分分钟的事情…加油!巨巨这个时候应该好好去刷难题了!刷难题才能有提升!!!

    //#include<bits/stdc++.h>
    #include<cstdio>
    #include<iostream>
    #include<math.h>
    #include<string.h>
    #include<algorithm>
    using namespace std;
    typedef long long LL;
    typedef unsigned long long ULL;
    const double eps=1e-5;
    const double pi=acos(-1.0);
    const int mod=1e8+7;
    const int INF=0x3f3f3f3f;
    
    const int N=1e3+10;
    int c[N];
    int m,n,k;
    
    struct asd{
        int x,y;
    };
    asd q[N*N];
    bool cmp(asd a,asd b)
    {
        if(a.x==b.x)return a.y<=b.y;
        else return a.x<b.x;
    }
    
    int Sum(int i)
    {
        int ans=0;
        while(i>0)
        {
            ans+=c[i];
            i-=i&(-i);
        }
        return ans;
    }
    
    void add(int i,int t)
    {
        while(i<=N-2)
        {
            c[i]+=t;
            i+=i&(-i);
        }
    }
    
    int main()
    {
        int t;
        int cas=1;
        scanf("%d",&t);
        while(t--)
        {
            scanf("%d%d%d",&n,&m,&k);
            for(int i=0;i<k;i++)
                scanf("%d%d",&q[i].x,&q[i].y);
    
            sort(q,q+k,cmp);
            memset(c,0,sizeof(c));
            LL ans=0;
            for(int i=0;i<k;i++)
            {
                add(q[i].y,1);
                ans+=Sum(m)-Sum(q[i].y);
            }
            printf("Test case %d: %lld
    ",cas++,ans);
        }
        return 0;
    }
  • 相关阅读:
    mybatis调用oracle存储过程
    java heap space
    汉字转拼音
    Go调用cpp类方式一
    ETCD节点故障恢复
    goroutine 加 channel 代替递归调用,突破递归调用的层级限制
    vscode debug golang
    mysql分组和去重同时使用
    github、gitlab 管理多个ssh key
    Qt连接MySQL
  • 原文地址:https://www.cnblogs.com/keyboarder-zsq/p/5934393.html
Copyright © 2011-2022 走看看