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$且$n\mod2=1)$的极限数据是跑不过去的……
所以就要打Subtask(子任务)。
因为:$200000 \times \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!(光速逃)

原文地址:https://www.cnblogs.com/luoshui-tianyi/p/12210606.html

时间: 2024-10-07 11:18:15

Luogu P1923 求第k小的数的相关文章

Quick-Select 1亿个数快速求第K小的数 分治法

Quick-Select  1亿个数快速求第K小的数  分治法 利用快速排序的思想,一开始选取中枢元,然后左右调整,接着比对中枢元p和K的大小,如果 p+1 = k (数组从0开始), 那么a[p] 就是答案,因为在p之前的,肯定都是小于a[p]的, 在p之后的,肯定大于p, 所以 a[p] 就是第 p+1 小.假如 p+1 不等于K, 那么根据大小,进行左右调整.调整过程中,理想状态下,每次都砍掉一半,数组的起始坐标要进行调整. 代码: // 快速排序法的修改 #include <iostre

*HDU2852 树状数组(求第K小的数)

KiKi's K-Number Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 3864    Accepted Submission(s): 1715 Problem Description For the k-th number, we all should be very familiar with it. Of course,to

soj3102 O(n)求第k小的数

原本觉得挺简单的,开始就一直RE,后来还T..发现是服务器可能出问题了,老化了,时间变慢了,拿以前A掉的代码来都是T. 不过还是有快的方法的. 就是位运算.另外stl里也有现成的函数可以用nth_element(s,s+k-1,s+n); 但是还有一个问题,nth_element()换成自己写的就T..无语了.. 大家快来指点一下啊~~~~ #include<iostream> #include<cstdio> #include<cstring> #include<

普林斯顿公开课 算法3-2:求第k大的数

问题 给定N个元素的数组,求第k大的数. 特例 当k=0时,就是求最大值,当k=N-1时,就是求最小值. 应用 顺序统计 求top N排行榜 基本思想 使用快速排序方法中的分区思想,使得a[k]左侧没有更小的数,右侧没有更大的数 性能 快速选择算法的复杂度是N. 最坏情况下的复杂度是1/2N^2,但是可以通过预先洗牌来防止出现最坏情况 代码 public class QuickSort { // 对区间 [start, end) 进行分区 public static int partition(

POJ 2761-Feed the dogs(划分树)求区间内第k小的数

Feed the dogs Time Limit: 6000MS   Memory Limit: 65536K Total Submissions: 17679   Accepted: 5561 Description Wind loves pretty dogs very much, and she has n pet dogs. So Jiajia has to feed the dogs every day for Wind. Jiajia loves Wind, but not the

最快效率求出乱序数组中第k小的数

题目:以尽量高的效率求出一个乱序数组中按数值顺序的第k 的元素值 思路:这里很容易想到直接排序然后顺序查找,可以使用效率较高的快排,但是它的时间复杂度是O(nlgn),我们这里可以用一种简便的方法,不一定需要排序,使用快速排序中双向分区的扫描方法,扫描出主元下标,然后根据主元的值将数组划分成一半大,一半小.然后再根据主元下标与k进行比较,如果相等,说明主元就是我们要找的数,如果大于k,说明k所代表的值在小的那边,继续向小的那部分递归,如果小于k,说明k代表的值在大的那边,继续向大的那部分递归.这

分治法题目整理分析 找第k小的数/求逆序对数目/派

设计一个平均时间为O(n)的算法,在n(1<=n<=1000)个无序的整数中找出第k小的数. 提示:函数int partition(int a[],int left,int right)的功能是根据a[left]~a[right]中的某个元素x(如a[left])对a[left]~a[right]进行划分,划分后的x所在位置的左段全小于等于x,右段全大于等于x,同时利用x所在的位置还可以计算出x是这批数据按升非降序排列的第几个数.因此可以编制int find(int a[],int left,

O(n)时间复杂度求最小的k个数和第k小的数

//思路:使用快速排序的patition函数来进行处理 时间复杂度为O(n) #include<iostream> #include<cassert> using namespace std; int partition(int *ar, int len, int low, int high) { int temp = ar[low]; while(low < high) { while(low < high && temp < ar[high])

求数组中第k小的数,有快速排序的验证

// The_Least_K_number.cpp : 定义控制台应用程序的入口点. //数组中第k小的数,例如a[1000]中第250小的数// #include "stdafx.h" #include <iostream> using namespace std; void swap(int *p, int *q) { int temp = *p; *p = *q; *q = temp; } int patition(int a[], int first, int la