zoukankan      html  css  js  c++  java
  • 树状数组学习笔记

    众所周知,树状数组是一个常用的数据结构。。。

    1.为啥用树状数组:

    如果用普通的前缀数组来维护前缀的信息,即使查询时o(1)的,但是修改就几乎要o(n),效率有时十分低下.

    而树状数组却弥补了这一缺点,修改和查询都是o(logn)的

    2.如何构建树状数组:

    根据二次幂的性质,我们可以把一个数转化成一个独一无二的二进制数,所以,我们可以建立一个类似于二进制数的数组来维护前缀和

    假如一个整数  x可以别分为x=2^i1+2^i2+2^i3...+2^im那么就可以把一个区间[1...x]分为(logx)的几个小区间

    假设i1>i2>i3>...im

    1长度为2^i1区间[1,2^i1]

    2长度为2^i2区间[2^i1+1,2^i1+2^i2]

    3长度为2^i3区间[2^i2+2^i1+1,2^i1+2^i2+2^i3]

    ...

    这些小区间的特点是长度为二进制分解下最小的二次幂,也就是lowbit(x);

     例如11=8+2+1=2^3+2^1+2^0,那么区间11可以分为[1,8],[9,10],[11],长度分别为lowbit(8)=8,lowbit(10)=2,lowbit(11)=1

     

     C[i]=A[i]+A[i-1]+A]i-lowbit(i)+1]

    C[1] = A[1];

    C[2] = A[1] + A[2];

    C[3] = A[3];

    C[4] = A[1] + A[2] + A[3] + A[4];

    C[5] = A[5];

    C[6] = A[5] + A[6];

    C[7] = A[7];

    C[8] = A[1] + A[2] + A[3] + A[4] + A[5] + A[6] + A[7] + A[8];

    求lowbit(i):

    lowbit(i)=i&(-i);

    修改

    void insert(long long x,long long vol) {
        while(x<=n) {
            c[x]+=vol;
            x+=lowbit(x);
        }
    }

    查询

    long long ask(long long x) {
        long long sum=0;
        while(x) {
            sum+=c[x];
            x-=lowbit(x);
        }
        return sum;
    }

    特别注意,树状数组的下标不能为0,比如insert(0,a)和ask(0)

    模板题https://www.luogu.com.cn/problem/P3374

    话不多说,直接上代码

    #include<bits/stdc++.h>
    using namespace std;
    long long lowbit(long long x) {
        return x&(-x);
    }
    long long n,m,c[1000000],a[1000000];
    void insert(long long x,long long vol) {
        while(x<=n) {
            c[x]+=vol;
            x+=lowbit(x);
        }
    }
    long long ask(long long x) {
        long long sum=0;
        while(x) {
            sum+=c[x];
            x-=lowbit(x);
        }
        return sum;
    }
    
    int main() {
        scanf("%lld%lld",&n,&m);
        for(long long i=1; i<=n; i++) {
            scanf("%lld",&a[i]);
            insert(i,a[i]);
        }
    
        for(long long i=1; i<=m; i++) {
            long long x,y,s;
            scanf("%lld%lld%lld",&s,&x,&y);
            if(s==1) insert(x,y);
            else {
                long long p=ask(y)-ask(x-1);
                printf("%lld
    ",p);
            }
        }
    
    }

     例题:https://www.luogu.com.cn/problem/P1428

     第一眼看到题目,就想到了暴力,发现就是求维护一个小于第i头鱼的可爱值的前缀,但如果数据范围大一些呢?

    仔细分析,这里我们就需要运用到树状数组

    样例:

    6
    4 3 0 5 1 2
    我们可以把每一个小鱼的权值当作当作树状数组的序号,把它的权值加上一,就相当于等于这个权值的小鱼多了一个,我们只需要找出小于这个权值的数目就行了
    #include<bits/stdc++.h>
    using namespace std;
    const int N=100001;
    int c[N],n,ans,b[N];
    int lowbit(int x)
    {
        return x&(-x);
    }
    struct data{
        int x,y;
    }a[N];
    bool cmp(data c,data d){
        if(c.x==d.x) return c.y<d.y;
        return c.x<d.x;
    }
    void insert(int x)
    {
        while(x<=N)
        {
            c[x]++;//为什么是加1呢就相当于是把等于这个权值的小鱼加1 
            x+=lowbit(x);
        }
    }
    int ask(int x)
    {
        long long sum=0;
        while(x)
        {
            sum+=c[x];
            x-=lowbit(x);
        }
        return sum;
    }
    int main(){
        scanf("%d",&n);
        for(int i=1;i<=n;i++){
            int x;
            scanf("%d",&x);
                cout<<ask(x)<<" ";//找出比x小的有多少 
            insert(x+1);//因为是统计小于这个数的,所以要加1,请仔细思考 
        }
    
    }

    如果数据太大,要用离散化

    #include<bits/stdc++.h>
    using namespace std;
    const int N=100001;
    int c[N],n,ans,b[N];
    int lowbit(int x)
    {
        return x&(-x);
    }
    struct data{
        int x,y;
    }a[N];
    bool cmp(data c,data d){
        if(c.x==d.x) return c.y>d.y; //这样可以保证严格小于的情况 
        return c.x<d.x;
    }
    void insert(int x)
    {
        while(x<=n)
        {
            c[x]++;
            x+=lowbit(x);
        }
    }
    int ask(int x)
    {
        long long sum=0;
        while(x)
        {
            sum+=c[x];
            x-=lowbit(x);
        }
        return sum;
    }
    int main(){
        scanf("%d",&n);
        a[0].x=-1;
        for(int i=1;i<=n;i++){
           scanf("%d",&a[i].x);
           a[i].y=i;//记录第i个数的坐标,因为在排序后会被打乱 
        }
        sort(a+1,a+n+1,cmp);
        int tot=1;
        for(int i=1;i<=n;i++){    
        b[a[i].y]=i; //a[i].y相当于第i小的数的坐标,把这个坐标设为第i小 
    }
    
        for(int i=1;i<=n;i++){
        cout<<ask(b[i])<<" ";//b[i]相当于第i个数是第几大的 
            insert(b[i]);
        }
    }

    但由于要排序,所以这个代码不快

    例题2:http://bzoj.org/p/1016

    首先,可以写dp

    转移方程轻易得出

    设s[i]是前i个数的前缀

    f[i]+=f[j](s[j]>s[i])

    #include<bits/stdc++.h>
    using namespace std;
    const long long N=5*1e5;
    const long long mod=1000000009;
    long long s[N],ans,n,a[N],f[N];
    int main(){
        scanf("%lld",&n);
        for(long long i=1;i<=n;i++)
        scanf("%lld",&a[i]),s[i]=s[i-1]+a[i];
        f[0]=1;
        for(int i=1;i<=n;i++)
        for(int j=0;j<i;j++)
        {
            if(s[i]>=s[j])
            f[i]=(f[i]+f[j])%mod;
        }
            cout<<f[n];
    }

     这一段其实可以用树状数组来优化

    只需要找到前面有多少比他小

    f[0]=1,所以当s[i]<0,那么这种情况就不可取,所以也要把0打入树状数组

    #include<bits/stdc++.h>
    using namespace std;
    const long long N=1e6+10,mod=1000000009;
    long long n,c[N],aa[N],ans;
    struct data{
        long long num,vol;
    }a[N];
    bool cmp(data c,data d){
        if(c.vol==d.vol) return c.num<d.num;
        else return c.vol<d.vol;
    }
    long long lowbit(long long x){
        return x&(-x);
    }
    void insert(long long x,long long vol){
        while(x<=N){
            c[x]=(c[x]+vol)%mod;
            x+=lowbit(x);
            
        }
    }
    long long ask(long long x){
        long long sum=0;
        while(x){
            sum=(sum+c[x])%mod;
            x-=lowbit(x);
        }
        return sum;
    }
    int main(){
        scanf("%lld",&n);    
        for(long long i=1;i<=n;i++)
        {
            long long x;
            scanf("%lld",&x);
            a[i].vol=a[i-1].vol+x;//算出前缀    
            a[i].num=i;    //标记位置,日常离散化 
        }
        sort(a+1,a+n+1,cmp);
        long long falg=0;//falg统计有多少个前缀比0小 (falg比flag好打,篡改单词) 
        for(int i=1;i<=n;i++){
        if(a[i].vol<0) falg++; 
    }
        falg++;//falg统计有多少个前缀比0小 ,所以0应该是第falg+1小 
        long long tot=0,i=0;//tot表示运行了多少次,应该要运行n+1次,因为还要包括0,i表示第i小的数 
        while(tot<=n+1){
        
        if(tot==falg) insert(falg,1);//为什么这里i不++呢,因为没有跳到下一个值,insert(falg,1)相当于把0放入树状数组,因为0是第flag小的 
        else aa[a[i].num]=tot,i++;
        tot++;
    }//日常离散化  
    
        for(long long i=1;i<=n;i++)
        {  
           long long f=ask(aa[i]);
           if(i==n) cout<<f;//输出f[n] 
           insert(aa[i],f);
        }
    
    }

    树状数组虽然快但是也有局限性,维护的东西特别少,不宜拓展

    谢谢阅读

    //黄鸡djskal爆蔡我

     

  • 相关阅读:
    How to build Linux system from kernel to UI layer
    Writing USB driver for Android
    Xposed Framework for Android 8.x Oreo is released (in beta)
    Linux Smartphone Operating Systems You Can Install Today
    Librem 5 Leads New Wave of Open Source Mobile Linux Contenders
    GUADEC: porting GNOME to Android
    Librem 5 – A Security and Privacy Focused Phone
    GNOME and KDE Join Librem 5 Linux Smartphone Party
    Purism计划推出安全开源的Linux Librem 5智能手机
    国产系统之殇:你知道的这些系统都是国外的
  • 原文地址:https://www.cnblogs.com/cwjr/p/13230091.html
Copyright © 2011-2022 走看看