zoukankan      html  css  js  c++  java
  • 可持久化线段树(主席树)——静态区间第k大

     主席树基本操作:静态区间第k大

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long LL;
    const int INF=1e9+7,MAXN=2e5+50,SIZE=MAXN*20;
    int N,M,K;
    int tmp[MAXN],A[MAXN],rt[MAXN],order[MAXN];
    int sz,lson[SIZE],rson[SIZE],sum[SIZE];
    int init(int l,int r){
        int cur=++sz;
        if(l<r){
            int mid=(l+r)>>1;
            lson[cur]=init(l,mid);
            rson[cur]=init(mid+1,r);
        }
        return cur;
    }
    int modify(int pre,int l,int r,int x){
        int cur=++sz;
        lson[cur]=lson[pre];
        rson[cur]=rson[pre];
        sum[cur]=sum[pre]+1;
        int mid=(l+r)>>1;
        if(l<r){
            if(x<=mid)
                lson[cur]=modify(lson[pre],l,mid,x);
            else
                rson[cur]=modify(rson[pre],mid+1,r,x);
        }
        return cur;
    }
    int query(int x,int y,int l,int r,int k){
        if(l==r)
            return l;
        int ii=sum[lson[y]]-sum[lson[x]],mid=(l+r)>>1;
        if(ii>=k){
            return query(lson[x],lson[y],l,mid,k);
        }else{
            return query(rson[x],rson[y],mid+1,r,k-ii);
        }
    }
    int main(){
        scanf("%d%d",&N,&K);
        for(int i=1;i<=N;i++){
            scanf("%d",tmp+i);
            A[i]=tmp[i];
        }
        sort(A+1,A+N+1);
        int M=unique(A+1,A+N+1)-A-1;
        rt[0]=init(1,M);
        for(int i=1;i<=N;i++){
            order[i]=lower_bound(A+1,A+M+1,tmp[i])-A;
            rt[i]=modify(rt[i-1],1,M,order[i]);
        }
        while(K--){
            int ii,jj,kk;
            scanf("%d%d%d",&ii,&jj,&kk);
            printf("%d
    ",A[query(rt[ii-1],rt[jj],1,M,kk)]);
        }
        return 0;
    }
    模板代码

    题面:给一个长为n的序列,m次询问,每次询问[l, r]内第k大的数是几。 n <= 100000, m <= 5000 

    主席树实现:参考bestFy (作者:bestFy,来源:CSDN )

    首先我们要将所有数字离散化。主席树相当于是在每个位置维护了一个线段树,线段树的节点是一个区间[x, y],这里的x和y都是离散后数的编号。

    当然如果真的在每个位置建一棵线段树,空间肯定爆炸,不过我们先不管这个问题。

    主席树节点中维护的值,是1~i之间这个区间内出现了数的次数。然后当我们查询的时候,就是利用到了前缀和的思想

    区间[x,y]中每个数字出现的次数即为第y棵线段树中的值-第x-1棵线段树中的值

    然后我们来模拟一下一组数据吧。

    7 1
    1 5 2 6 3 7 4
    2 5 3 

    首先建树。

     

    然后我们逐一插入数字。

    将每个数字的大小(即离散化后的编号)插入到它的位子上,然后并把所有包括它的区间的sum都++。

     

      

    。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

    直到把所有数字插入以后,情况是这样的。

     

    那么建树的具体过程大致如上

    接下来我们考虑查询。

    要查询[2, 5]中第3大的数我们首先把第1棵线段树和第5棵拿出来。

     

    然后我们发现,将对应节点的数相减,刚刚好就是[2, 5]内某个范围内的数的个数。比如[1, 4]这个节点相减是2,就说明[2. 5]内有2个数是在1~4范围内(就是2, 3)。

    所以对于一个区间[l, r],我们可以每次算出在[l, mid]范围内的数,如果数量>=k(k就是第k大),就往左子树走,否则就往右子树走。

    代码实现

    我们用动态开点线段树,定义一些变量

    int N:数字总数,M:离散化后数字总数,K:查询数量;
    int tmp[MAXN]:记录数值大小,A[MAXN]:排序的数值,rt[MAXN]:每一个节点的线段树的根节点,order[MAXN]:每一个数字的排名;
    int sz:线段树节点总数,lson[SIZE]:左儿子下标,rson[SIZE]:右儿子下标,sum[SIZE]:统计和;

    建树:先建一棵空的权值线段树,所有点的值都是0

    int init(int l,int r){
        int cur=++sz;
        if(l<r){
            int mid=(l+r)>>1;
            lson[cur]=init(l,mid);
            rson[cur]=init(mid+1,r);
        }
        return cur;
    }

    插入

    我们将N个数离散化,按照排名统计到N棵线段树上

    for(int i=1;i<=N;i++){
        scanf("%d",tmp+i);
        A[i]=tmp[i];
    }
    sort(A+1,A+N+1);
    int M=unique(A+1,A+N+1)-A-1;
    rt[0]=init(1,M);
    for(int i=1;i<=N;i++){
        order[i]=lower_bound(A+1,A+M+1,tmp[i])-A;
        rt[i]=modify(rt[i-1],1,M,order[i]);
    }

    每次建立一棵新的线段树,但如果建一棵全新的,空间和时间都会爆炸

    因为我们更新一个节点的值,只有一条logM的链上的值会改变,所以没有更改的节点都可以使用上一棵树的

    每一次只要按照线段树的方式遍历,需要加的儿子则新建,否则将儿子指向上一棵树的儿子

    int modify(int pre,int l,int r,int x){
        int cur=++sz;
        lson[cur]=lson[pre];
        rson[cur]=rson[pre];
        sum[cur]=sum[pre]+1;
        int mid=(l+r)>>1;
        if(l<r){
            if(x<=mid)
                lson[cur]=modify(lson[pre],l,mid,x);
            else
                rson[cur]=modify(rson[pre],mid+1,r,x);
        }
        return cur;
    }

    查询:

    因为第i棵线段树每个节点的值为区间[1,i]上每个数字出现的数字,所以第y棵树的每个节点的值-第x-1棵树的每个节点的值即为区间[x,y]上每一个数字出现的次数

    while(K--){
        int ii,jj,kk;
        scanf("%d%d%d",&ii,&jj,&kk);
        printf("%d
    ",A[query(rt[ii-1],rt[jj],1,M,kk)]);
    }

    每一次在权值线段树上查询区间第k大:

    int query(int x,int y,int l,int r,int k){
        if(l==r)
            return l;
        int ii=sum[lson[y]]-sum[lson[x]],mid=(l+r)>>1;
        if(ii>=k){
            return query(lson[x],lson[y],l,mid,k);
        }else{
            return query(rson[x],rson[y],mid+1,r,k-ii);
        }
    }

    最终代码:

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 typedef long long LL;
     4 const int INF=1e9+7,MAXN=2e5+50,SIZE=MAXN*20;
     5 int N,M,K;
     6 int tmp[MAXN],A[MAXN],rt[MAXN],order[MAXN];
     7 int sz,lson[SIZE],rson[SIZE],sum[SIZE];
     8 int init(int l,int r){
     9     int cur=++sz;
    10     if(l<r){
    11         int mid=(l+r)>>1;
    12         lson[cur]=init(l,mid);
    13         rson[cur]=init(mid+1,r);
    14     }
    15     return cur;
    16 }
    17 int modify(int pre,int l,int r,int x){
    18     int cur=++sz;
    19     lson[cur]=lson[pre];
    20     rson[cur]=rson[pre];
    21     sum[cur]=sum[pre]+1;
    22     int mid=(l+r)>>1;
    23     if(l<r){
    24         if(x<=mid)
    25             lson[cur]=modify(lson[pre],l,mid,x);
    26         else
    27             rson[cur]=modify(rson[pre],mid+1,r,x);
    28     }
    29     return cur;
    30 }
    31 int query(int x,int y,int l,int r,int k){
    32     if(l==r)
    33         return l;
    34     int ii=sum[lson[y]]-sum[lson[x]],mid=(l+r)>>1;
    35     if(ii>=k){
    36         return query(lson[x],lson[y],l,mid,k);
    37     }else{
    38         return query(rson[x],rson[y],mid+1,r,k-ii);
    39     }
    40 }
    41 int main(){
    42     scanf("%d%d",&N,&K);
    43     for(int i=1;i<=N;i++){
    44         scanf("%d",tmp+i);
    45         A[i]=tmp[i];
    46     }
    47     sort(A+1,A+N+1);
    48     int M=unique(A+1,A+N+1)-A-1;
    49     rt[0]=init(1,M);
    50     for(int i=1;i<=N;i++){
    51         order[i]=lower_bound(A+1,A+M+1,tmp[i])-A;
    52         rt[i]=modify(rt[i-1],1,M,order[i]);
    53     }
    54     while(K--){
    55         int ii,jj,kk;
    56         scanf("%d%d%d",&ii,&jj,&kk);
    57         printf("%d
    ",A[query(rt[ii-1],rt[jj],1,M,kk)]);
    58     }
    59     return 0;
    60 }
    View Code
  • 相关阅读:
    原型设计作业
    案例分析作业
    编程作业
    阅读任务
    自我介绍
    5 20210420-1 团队作业1—团队展示
    3 20210405-1 案例分析作业
    阅读任务
    自我介绍
    原型设计
  • 原文地址:https://www.cnblogs.com/guoshaoyang/p/10889324.html
Copyright © 2011-2022 走看看