poj2828(线段树查找序列第k小的值)

题目链接:https://vjudge.net/problem/POJ-2828

题意:有n个人,依次给出这n个人进入队列时前面有多少人p[i],和它的权值v[i],求最终队列的权值序列。

思路:基本类似于poj2182,简化题意后即为求序列1..n中第k小的值的问题。读入数据量比较大,最好读入优化。我们从n..1逆序遍历,则可以确认最后一个人的最终位置为p[n]+1,然后从序列中删除p[n]+1,继续操作倒数第二个...这一操作可以通过线段树来完成。线段树的结点包括3个值:l(区间左端点),r(区间右端点),len(区间剩余编号数)。每次询问维护len值。时间复杂度为O(Tnlogn)。

AC代码:

#include<cstdio>
#include<cctype>
using namespace std;
const int maxn=200005;

inline int read(){
    int x=0,f=0;char c=0;
    while(!isdigit(c)) {f|=c=='-';c=getchar();}
    while(isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return f?-x:x;
}

struct node{
    int l,r,len;
}tr[4*maxn];

int p[maxn],v[maxn],n,ans[maxn];

void build(int v,int l,int r){
    tr[v].l=l,tr[v].r=r,tr[v].len=r-l+1;
    if(l==r) return;
    int mid=(l+r)>>1;
    build(2*v,l,mid);
    build(2*v+1,mid+1,r);
}

int query(int v,int k){
    --tr[v].len;
    if(tr[v].l==tr[v].r) return tr[v].l;
    if(k<=tr[2*v].len) return query(2*v,k);
    else return query(2*v+1,k-tr[2*v].len);
}

int main(){
    while(~scanf("%d",&n)){
        build(1,1,n);
        for(int i=1;i<=n;++i)
            p[i]=read(),v[i]=read();
        for(int i=n;i>=1;--i)
            ans[query(1,p[i]+1)]=v[i];
        for(int i=1;i<=n;++i)
            printf("%d ",ans[i]);
        printf("\n");
    }
    return 0;
}

 

posted @ 2019-04-27 15:51  Frank__Chen  阅读(326)  评论(0编辑  收藏  举报