区间第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;}
这是数据结构专题的第一篇,之后还有其他的,都是一些数据结构来优化程序。
- 区间第K大值
- 区间第K大
- 第K大区间
- 区间第k大
- 静态区间第K大
- poj2104 区间第K大
- 区间第K值
- 划分树——求区间第k大值
- POJ 2104 区间第K大值(划分树做法)
- 主席树 --- 求区间第k大值
- 区间第k大值(主席树入门)
- 区间第K大(划分树)
- ZOJ2112(区间动态求第K大)
- 区间第k大(poj 2104)
- 带修改的区间第k大
- poj2104(区间第k大+离散化)
- 线段树求解区间第k大
- 51nod1686 第K大区间
- spring使用aop时需要设置proxy-target-class="true" 否则无法依赖注入
- iOS window添加视频view或图片view,并有切换根视图控制器的时候注意
- list,set,map区别
- 外部访问 Vue 中的 methods方法及其属性
- 图的简单实现
- 区间第K大值
- 学习rabbitmq(1) AMQP
- 目前很多应用直接使用手机验证码登录?无登录密码?这样做有什么利弊?
- awk更新现有文件内容并输出到新文件中
- PTA 7-2 家谱处理——模拟
- Spark Streaming 'numRecords must not be negative'问题解决
- 利用Wifidog实现微信wifi连接以及自写认证服务器
- Ubuntu 批量杀Node 进程
- HTML-块级元素与行内元素(block-level & inline element)