zoukankan      html  css  js  c++  java
  • 主席树(可持久化线段树)

    经典问题:求全局第K大

    思路:可以在权值线段树上二分,当左儿子存储的个数大于k时在左儿子寻找,否则将k减去左儿子存储的个数在右儿子寻找

    主席树经典题:求区间第k大

    如果我们像全局第K大一样给每个区间建一个线段树是不可能的,考虑做一个前缀和,用第R个线段树减去第L-1个线段树就是[L,R]的线段树,再做全局第K大即可

    但是仍然会MLE,事实上不需要每个数建一棵线段树,每次加入只需要插入的路径上更新即可,所以每次的空间复杂度是O(logn)

    这样做实际上可以回到任何一个历史状态,这就是所谓的可持久化线段树——主席树

    需要注意的是,因为要更新路径,我们按照遍历的顺序每到一个新节点编号加一(不同于传统线段树左儿子是P>>1右儿子是P>>1|1的编号方式)

     数组注释:lc[i]表示i的左儿子,rc[i]表示i的右儿子,rt[i]表示第i次更改时的根编号

    build函数

    void build(int &t,int l,int r)
    {
        t=++cnt;//每访问一个新的节点就编号加一 
        if(l==r) return;
        int mid=(l+r)>>1;
        build(lc[t],l,mid);
        build(rc[t],mid+1,r);
    }

    比较简单,需要注意是按遍历顺序开点

    modify函数(insert)

    int modify(int o,int l,int r)//o表示上一个状态的编号 
    {
        int h=++cnt;
        lc[h]=lc[o]; rc[h]=rc[o];/*左右儿子都复制一下*/ sums[h]=sums[o]+1;//当前节点处于插入路径上,需要更新 
        if(l==r) return h;
        int mid=(l+r)>>1;
        if(place<=mid) lc[h]=modify(lc[h],l,mid);//将lc[h]更新,成链状 
        else rc[h]=modify(rc[h],mid+1,r);
        return h;
    }

    比较重要的函数,可以结合图片理解

    以下是插入数字4时的情况

    query函数

    //u是l-1时的线段树,v是r时的线段树
    //query(rt[l-1],rt[r],1,num,k) 
    int query(int u,int v,int l,int r,int k)
    {
        int ans,mid=(l+r)>>1;
        int x=sums[lc[v]]-sums[lc[u]];//相当于[l,r]线段树lc[x]所存的个数 
        if(l==r) return l;//找到了第k小 
        if(x>=k) ans=query(lc[u],lc[v],l,mid,k);
        else ans=query(rc[u],rc[v],mid+1,r,k-x);
        return ans; //别忘了return 回去 
    }

    前缀和的思想,前缀减前缀,区间变全局

    code

    #include<cstdio>
    #include<iostream>
    #include<algorithm> 
    #define N 500005
    using namespace std;
    int read()
    {
        int x=0,f=1; char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
        while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
        return x*f;
    }
    int a[N],index[N],rt[N],lc[N<<3],b[N];
    int l,r,k,ans,num,place,n,m;
    int cnt,sums[N<<3],rc[N<<3];
    void build(int &t,int l,int r)
    {
        t=++cnt;//每访问一个新的节点就编号加一 
        if(l==r) return;
        int mid=(l+r)>>1;
        build(lc[t],l,mid);
        build(rc[t],mid+1,r);
    }
    int modify(int o,int l,int r)//o表示上一个状态的编号 
    {
        int h=++cnt;
        lc[h]=lc[o]; rc[h]=rc[o];/*左右儿子都复制一下*/ sums[h]=sums[o]+1;//当前节点处于插入路径上,需要更新 
        if(l==r) return h;
        int mid=(l+r)>>1;
        if(place<=mid) lc[h]=modify(lc[h],l,mid);//将lc[h]更新,成链状 
        else rc[h]=modify(rc[h],mid+1,r);
        return h;
    }
    //u是l-1时的线段树,v是r时的线段树
    //query(rt[l-1],rt[r],1,num,k) 
    int query(int u,int v,int l,int r,int k)
    {
        int ans,mid=(l+r)>>1;
        int x=sums[lc[v]]-sums[lc[u]];//相当于[l,r]线段树lc[x]所存的个数 
        if(l==r) return l;//找到了第k小 
        if(x>=k) ans=query(lc[u],lc[v],l,mid,k);
        else ans=query(rc[u],rc[v],mid+1,r,k-x);
        return ans; //别忘了return 回去 
    }
    int main()
    {
        n=read(); m=read();
        for(register int i=1;i<=n;++i){a[i]=read();b[i]=a[i];}
        sort(b+1,b+n+1);
        num=unique(b+1,b+n+1)-b-1;//离散化 
        build(rt[0],1,num);
        for(register int i=1;i<=n;i++)
        {
            place=lower_bound(b+1,b+num+1,a[i])-b;
            rt[i]=modify(rt[i-1],1,num); 
        }
        while(m--)
        {
            l=read(); r=read(); k=read();
            ans=query(rt[l-1],rt[r],1,num,k);
            printf("%d
    ",b[ans]); 
        }
        return 0;
    } 
  • 相关阅读:
    dart 函数迭代器
    dart 编译
    dart 扩展方法
    dart 包
    默认2345导航
    (24)WPF 数据绑定
    (22)WPF 控件模板
    JSP慕课网之Session
    HTML <td> 标签的 colspan 属性
    HTML Input属性
  • 原文地址:https://www.cnblogs.com/Liuz8848/p/10454464.html
Copyright © 2011-2022 走看看