码迷,mamicode.com
首页 > 其他好文 > 详细

Luogu P1923 求第k小的数

时间:2020-01-19 00:11:54      阅读:116      评论:0      收藏:0      [点我收藏+]

标签:序列   update   fine   its   $2   cpp   ble   mod   lse   

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!(光速逃)

Luogu P1923 求第k小的数

标签:序列   update   fine   its   $2   cpp   ble   mod   lse   

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

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!