zoukankan      html  css  js  c++  java
  • Luogu P1923 求第k小的数

    Luogu P1923 求第k小的数

    一看这题,静态查询区间第$k$小的数,不就是可持久化线段树(主席树)的模板题吗?!(误)
    直接把主席树的板子打上来♪:

    #include<bits/stdc++.h>
    #define N 200010
    #define mid ((l+r)>>1)
    
    using namespace std;
    
    int n,m,l,r,k,ans,id,siz;
    int a[N],b[N];
    
    struct segmenttree {
    	int ls,rs,sum,root;
    }tree[N*40];
    
    void Build(int &o,int l,int r) {
    	id++;
    	o=id;
    	tree[o].sum=0;
    	if(l==r) {
    		return;
    	}
    	Build(tree[o].ls,l,mid);
    	Build(tree[o].rs,mid+1,r);
    	return;
    }
    
    void Update(int &o,int l,int r,int pre,int x) {
    	id++;
    	o=id;
    	tree[o].ls=tree[pre].ls;
    	tree[o].rs=tree[pre].rs;
    	tree[o].sum=tree[pre].sum+1;
    	if(l==r) {
    		return;
    	}
    	if(x<=mid) {
    		Update(tree[o].ls,l,mid,tree[pre].ls,x);
    	}
    	else {
    		Update(tree[o].rs,mid+1,r,tree[pre].rs,x);
    	}
    	return;
    }
    
    int Find(int x,int y,int l,int r,int k) {
    	if(l==r) {
    		return l;
    	}
    	int cnt=tree[tree[y].ls].sum-tree[tree[x].ls].sum;
    	if(k<=cnt) {
    		return Find(tree[x].ls,tree[y].ls,l,mid,k);
    	}
    	else {
    		return Find(tree[x].rs,tree[y].rs,mid+1,r,k-cnt);
    	}
    }
    
    int main()
    {
    	scanf("%d%d",&n,&k);
    	for(int i=1;i<=n;i++) {
    		scanf("%d",&a[i]);
    		b[i]=a[i];
    	}
    	sort(b+1,b+n+1);
    	siz=unique(b+1,b+n+1)-b-1;
    	Build(tree[0].root,1,siz);
    	for(int i=1;i<=n;i++) {
    		int val=lower_bound(b+1,b+siz+1,a[i])-b;
    		Update(tree[i].root,1,siz,tree[i-1].root,val);
    	}
    	l=1,r=n;
    	ans=Find(tree[l-1].root,tree[r].root,1,siz,k+1);
    	printf("%d",b[ans]);
    	return 0;
    }
    

    然后……然后?就得到了这个:60分,T后两个点……
    我们知道,主席树的复杂度为$O((N+M) log N)$,其中$N$为序列长度,$M$为查询次数。这道题只用查询一次,即从$1$到$n$的区间第$k$小,所以该做法复杂度为$O(Nlog N)$。
    但显然,对于$n=4999999(n<5000000$且$nmod2=1)$的极限数据是跑不过去的……
    所以就要打Subtask(子任务)。
    因为:$200000 imes log(200000) approx 1060206 approx 1000000$.
    所以对于$n leq 200000$的数据都可以用主席树。
    那对于$n geq 200000$的数据,我们可以用STL里一个叫做nth_element的东西。
    这个东西的用法是这样:

    nth_element(a+l,a+k,a+r);
    

    这样它会使$a$这个数组中,区间$[l,r)$内的第$k$小的元素处在第$k$个位置上(相对位置),但是它并不保证其他元素有序!它的复杂度是$O(r-l+1)$,即区间长。
    那么这部分的代码如下:

    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++) {
    	scanf("%d",&a[i]);
    }
    k++;
    nth_element(a+1,a+k,a+n+1);
    printf("%d",a[k]);
    

    完整代码如下:

    #include<bits/stdc++.h>
    #define N 200010
    #define mid ((l+r)>>1)
    
    using namespace std;
    
    int n,m,l,r,k,ans,id,siz;
    int a[N],b[N];
    
    struct segmenttree {
    	int ls,rs,sum,root;
    }tree[N*40];
    
    void Build(int &o,int l,int r) {
    	id++;
    	o=id;
    	tree[o].sum=0;
    	if(l==r) {
    		return;
    	}
    	Build(tree[o].ls,l,mid);
    	Build(tree[o].rs,mid+1,r);
    	return;
    }
    
    void Update(int &o,int l,int r,int pre,int x) {
    	id++;
    	o=id;
    	tree[o].ls=tree[pre].ls;
    	tree[o].rs=tree[pre].rs;
    	tree[o].sum=tree[pre].sum+1;
    	if(l==r) {
    		return;
    	}
    	if(x<=mid) {
    		Update(tree[o].ls,l,mid,tree[pre].ls,x);
    	}
    	else {
    		Update(tree[o].rs,mid+1,r,tree[pre].rs,x);
    	}
    	return;
    }
    
    int Find(int x,int y,int l,int r,int k) {
    	if(l==r) {
    		return l;
    	}
    	int cnt=tree[tree[y].ls].sum-tree[tree[x].ls].sum;
    	if(k<=cnt) {
    		return Find(tree[x].ls,tree[y].ls,l,mid,k);
    	}
    	else {
    		return Find(tree[x].rs,tree[y].rs,mid+1,r,k-cnt);
    	}
    }
    
    void Subtask1() {
    	for(int i=1;i<=n;i++) {
    		scanf("%d",&a[i]);
    		b[i]=a[i];
    	}
    	sort(b+1,b+n+1);
    	siz=unique(b+1,b+n+1)-b-1;
    	Build(tree[0].root,1,siz);
    	for(int i=1;i<=n;i++) {
    		int val=lower_bound(b+1,b+siz+1,a[i])-b;
    		Update(tree[i].root,1,siz,tree[i-1].root,val);
    	}
    	l=1,r=n;
    	ans=Find(tree[l-1].root,tree[r].root,1,siz,k+1);
    	printf("%d",b[ans]);
    	return;
    }
    
    void Subtask2() {
    	for(int i=1;i<=n;i++) {
    		scanf("%d",&a[i]);
    	}
    	k++;
    	nth_element(a+1,a+k,a+n+1);
    	printf("%d",a[k]);
    	return;
    }
    
    int main()
    {
    	scanf("%d%d",&n,&k);
    	if(n<=200000) {
    		Subtask1();
    	}
    	else {
    		Subtask2();
    	}
    	return 0;
    }
    

    AC记录:1.04s
    我知道各位大佬一定有比我更好的方法,欢迎吊打,QAQ!(光速逃)

  • 相关阅读:
    结巴分词
    Python如何将RGB图像转换为Pytho灰度图像?
    多套方案来提高python web框架的并发处理能力
    python使用web.py开发httpserver,解决post请求跨域问题
    关于socket知识整理
    SQL注入实验
    什么是内存(一):存储器层次结构
    内存剖析2
    JDBC的事务处理 JDBC事务处理 JDBC教程
    SQL注入绕过的技巧总结
  • 原文地址:https://www.cnblogs.com/luoshui-tianyi/p/12210606.html
Copyright © 2011-2022 走看看