区间第K大值

来源:互联网 发布:python安装第三方库 编辑:程序博客网 时间:2024/04/30 10:27

数据结构专题

做法有多种:
1、二分+分块
2、二分+归并树
3、划分树
4、主席树

其中第3种我没有写。
这四种方法各自的时间空间复杂度都不一样,推广性也不一样。

时间:
二分+分块:O(nlog√(nlogn)+m√nlog1.5n)
二分+归并树:O(nlogn+mlog3n)
主席树:O( (n+m)logn )

推广性:
二分+分块:推广性强,效率不高
二分+归并树:推广性弱,效率较高
主席树:推广性极强,效率极高

现在分别讲解这三种方法

一、二分+分块:

分两块:二分和分块(说和没说一样)

我们发现一个对于一个数x,x越大,那么他的排名就越靠前,那么就是线性的,可以用二分。

二分后就要判断其是第几大数。如果一个一个判断过来,不如直接写。可以用分块。把整个分成几块后,将每一块里的数都进行排序。排序有什么好处?可以用二分查找(lower_ bound或upper_ bound,具体看题目)。这样就很高效了。别忘了一些不在完整块里的残留要循环过去。

还有一点是要离散,数据很大,不离散内存会炸。

Code:

#include<bits/stdc++.h>using namespace std;int A[30005],B[30005],n,cas,N,C[30005];bool check(int l,int r,int x,int P){//分块求值    int L=l/N,R=r/N,res=0;    for(int i=L+1;i<R;i++){//完整块内        int sum=lower_bound(A+i*N+1,A+(i+1)*N+1,x)-A-1-i*N;        res+=sum;    }    if(L!=R){//块外        for(int i=l;i<=(L+1)*N;i++)if(C[i]<x)res++;        for(int i=R*N+1;i<=r;i++)if(C[i]<x)res++;    }else{        for(int i=l;i<=r;i++)if(C[i]<x)res++;    }    res=r-l+1-res;    if(res>=P)return 1;    return 0;}int main(){    scanf("%d%d",&n,&cas);    for(int i=1;i<=n;i++)scanf("%d",&A[i]),B[i]=A[i],C[i]=A[i];    sort(B+1,B+1+n);    N=sqrt(n);//这样的N其实不是最优的,最优的是N=sqrt(n*log2(n)),可以手推一下在这道题里这样最快    for(int i=0;i<N;i++)sort(A+N*i+1,A+N*(i+1)+1);//每一块排序    sort(A+N*N+1,A+1+n);//最后一块不完整,单独排序    while(cas--){        int l,r,k;        scanf("%d%d%d",&l,&r,&k);        int L=1,R=n,ans;        while(L<=R){//二分枚举一个x            int mid=(L+R)/2;            if(check(l,r,B[mid],k))L=mid+1,ans=mid;            else R=mid-1;        }        printf("%d\n",B[ans]);//找到的只是离散后的下标    }    return 0;}

二、二分+归并树

二分和上面的是一样的。

归并树是新学的。个人的理解是把归并排序时这一层的状态全部记录下来,而不是全部排完。这样就可以按照线段树的查询方式往下递归,然后再用二分查找求答案。

Code:

#include<bits/stdc++.h>#define M 30005using namespace std;int A[M],S[20][M],B[M];struct node{int L,R;}tree[M*4];void Build(int L,int R,int p,int dep){//建树    tree[p].L=L;tree[p].R=R;    if(L==R){        S[dep][L]=A[L];        return;    }    int mid=(L+R)/2;    Build(L,mid,p*2,dep+1);Build(mid+1,R,p*2+1,dep+1);//先把后面的算出来,和归并排序时一样的    int i=L,j=mid+1,k=L;    while(i<=mid&&j<=R)        if(S[dep+1][i]<S[dep+1][j])S[dep][k++]=S[dep+1][i++];        else S[dep][k++]=S[dep+1][j++];    while(i<=mid)S[dep][k++]=S[dep+1][i++];    while(j<=R)S[dep][k++]=S[dep+1][j++];}int Query(int L,int R,int p,int dep,int x){//和线段树查询基本一样    if(tree[p].L==L&&tree[p].R==R){        return lower_bound(S[dep]+L,S[dep]+R+1,x)-S[dep]-L;    }    int mid=(tree[p].L+tree[p].R)/2;    if(mid>=R)return Query(L,R,p*2,dep+1,x);    else if(mid<L)return Query(L,R,p*2+1,dep+1,x);    else return Query(L,mid,p*2,dep+1,x)+Query(mid+1,R,p*2+1,dep+1,x);}int main(){    int n,cas;    scanf("%d%d",&n,&cas);    for(int i=1;i<=n;i++)scanf("%d",&A[i]),B[i]=A[i];    sort(B+1,B+1+n);    Build(1,n,1,1);    while(cas--){//此段与上一个同类        int l,r,x;        scanf("%d%d%d",&l,&r,&x);        int L=1,R=n,ans=0;        while(L<=R){            int mid=(L+R)/2;            int res=r-l+1-Query(l,r,1,1,B[mid]);            if(res>=x)L=mid+1,ans=mid;            else R=mid-1;        }        printf("%d\n",B[ans]);    }    return 0;}

三、主席树

这个才是本篇的重点。虽然我依旧不是很会主席树,但是手推的话大致也是懂得了那么一点点。主席树就是一种权值前缀和,但是如果把每个前缀和造成一棵树,那么内存就太耗了。如果把数全部画出来后,就可以发现其实有很多都是重复的。那么如何利用这些重复的点?很容易想到的就是共用这些点。那么如何实现?其实我也不是很会……(读者还是上网自己查吧,等以后我再补)

这道题的主席树是静态的,即不能更新(插入不算更新),反正这道题只有查询。

Code:

    #include<bits/stdc++.h>#define M 30005using namespace std;int A[M],B[M],Lt[M*20],Rt[M*20],Gt[M*20],Sum[M*20],tot;//tot是节点的编号void Build(int L,int R,int &tid){//造树    tid=++tot;Sum[tid]=0;    if(L==R)return;    int mid=(L+R)/2;    Build(L,mid,Lt[tid]);Build(mid+1,R,Rt[tid]);}void Insert(int lat,int L,int R,int x,int &tid){//插入一个新数    tid=++tot;    Lt[tid]=Lt[lat];Rt[tid]=Rt[lat];//这些大概是就是把不共用的造出来,公用的不变    Sum[tid]=Sum[lat]+1;    if(L==R)return;    int mid=(L+R)/2;    if(mid>=x)Insert(Lt[lat],L,mid,x,Lt[tid]);    else Insert(Rt[lat],mid+1,R,x,Rt[tid]);}int Query(int lt,int rt,int L,int R,int x){    if(L==R)return L;    int cnt=Sum[Lt[rt]]-Sum[Lt[lt]];    int mid=(L+R)/2;    if(x<=cnt)return Query(Lt[lt],Lt[rt],L,mid,x);    else return Query(Rt[lt],Rt[rt],mid+1,R,x-cnt);}int main(){    int n,cas;    scanf("%d%d",&n,&cas);    for(int i=1;i<=n;i++)scanf("%d",&A[i]),B[i]=A[i];    sort(B+1,B+1+n);int m=unique(B+1,B+1+n)-B-1;//离线去重都是必须的    for(int i=1;i<=n;i++)A[i]=lower_bound(B+1,B+1+m,A[i])-B;    Build(1,m,Gt[0]);//现造一棵空树    for(int i=1;i<=n;i++)Insert(Gt[i-1],1,m,A[i],Gt[i]);//每次插入一个点    while(cas--){        int l,r,x;        scanf("%d%d%d",&l,&r,&x);        printf("%d\n",B[Query(Gt[l-1],Gt[r],1,m,r-l+2-x)]);    }    return 0;}

这是数据结构专题的第一篇,之后还有其他的,都是一些数据结构来优化程序。

原创粉丝点击