POJ 2828 Buy Tickets (线段树 单点更新-查找第k大元素)

题目链接

POJ2828

题目大意

火车站有n(n 200000)个人要排队,它们是按顺序到达,到达后要插队,现在告诉你每个人每次插队排在当前队伍第几个人的后面以及它们的权值,输出最终队伍的权值序列。

分析

按照题意,每个人插队时的位置都是相对那个时候的队伍状态而言的,因此是动态变化,但是经过分析我们可以发现,最后一个人的位置是一定确定的,因此我们可以倒序考虑每个人的插队,依次确定每个人的位置。举个栗子:比如现在我考虑第n-1个人的插队,它要插到他到达时队伍第x个人的后面,而此时第n个人的位置已经确定,相当于那个位置已经被占领,我们只需在剩余的位置中找到第x+1个给第n-1个人即可,以此类推。
所以我们可以用线段树维护区间中剩余位置的数量sum[],插队时相当于单点更新,点的位置相当于查找第K大元素当左子区间中剩余元素大于等于K时,就在左子区间中查找第K个位置,否则就在右子区间中查找第K-sum[rs]个位置,找到位置后更新权值。最后遍历线段树,依次输出叶子节点的权值即可。

代码

#include <iostream>
#include <cstdio>
#define ls (rt<<1)
#define rs (rt<<1|1)
using namespace std;

const int MAXN=200010;
struct Node
{
    int p,v;
}node[MAXN];//记录每个人的插队信息
int n,sum[MAXN<<2],val[MAXN<<2],cnt;
//sum[]用线段树维护区间中剩余位置数,val[]为结点权值
void PushUp(int rt)
{
    sum[rt]=sum[ls]+sum[rs];
}
void Build(int l,int r,int rt)
{
    if (l==r)
    {
        sum[rt]=1;//初始都有空位
        return;
    }
    int mid=(l+r)>>1;
    Build(l,mid,ls);
    Build(mid+1,r,rs);
    PushUp(rt);
}
void Update(int p,int v,int l,int r,int rt)//在区间[l,r]中找第p个剩余位,把它的权值更新为v
{
    if (l==r)
    {
        if (sum[rt]==1)
        {
            sum[rt]=0;
            val[rt]=v; //占位并更新权值
        }
        return;
    }
    int mid=(l+r)>>1;
    if (p<=sum[ls])   //查找第K大元素的思想
        Update(p,v,l,mid,ls);
    else
        Update(p-sum[ls],v,mid+1,r,rs);
    PushUp(rt);
}
void Print(int l,int r,int rt)//遍历线段树结点,输出叶子结点权值
{
    if (l==r)
    {
        cnt++;
        printf("%d",val[rt]);
        if (cnt<n)
            printf(" ");
        else
            printf("\n");
        return;
    }
    int mid=(l+r)>>1;
    Print(l,mid,ls);
    Print(mid+1,r,rs);
}
int main()
{
    int i;
    while (scanf("%d",&n)!=EOF)
    {
        for (i=1;i<=n;i++)
        {
            scanf("%d%d",&node[i].p,&node[i].v);
            node[i].p++;//排在第p个人后面就是要占第p+1个位置
        }
        Build(1,n,1);
        for (i=n;i>=1;i--) //倒序插队
        {
            int p=node[i].p;
            int v=node[i].v;
            Update(p,v,1,n,1);
        }
        cnt=0;
        Print(1,n,1);
    }
    return 0;
}

注:当输入输出较多时,避免使用输入输出流,容易TLE.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值