poj-2828 Buy Tickets【线段树】

很经典的一道线段树问题,以前都没想到到线段树可以这样用。
/*Description:
春节将要到来了。小H希望回家过年。然后大家都知道火车是非常的难买,经常要排很长的队,但是小H为了买火车票拼了,不怕排队。
但是小H到了火车站的时候天已经黑了,他感觉非常的冷。他想为什么不找个问题思考来遗忘寒冷呢。然后他就想到了自己排队这个问题。
他想如果排队的每个人都有个固定的颜值,并且自己排队的时候经常有人插队,给出插队的人的位置和颜值,那么最后的颜值序列是多少呢?

Input:
有多组测试数据。
每组测试数据第一行为N (1<=N<=200000),表示有多少个人插队。
接下有的N行有一对数,第一个数表示插队的人要插得位置pos(0<=pos),第二个数表示他的颜值val(0<=val<=32767)。

Output:
输出最终的颜值序列。

Sample Input:
4
0 77
1 51
1 33
2 69
4
0 20523
1 19243
1 3890
0 31492

Sample Output:
77 33 69 51
31492 20523 3890 19243

友情提示:

就第一组数据来说,
第一个颜值为77的插入第0个位置之后序列是:77
第二个颜值为51的插入第1个位置之后序列是:77 51
第三个颜值为33的插入第1个位置之后序列是:77 33 51
第四个颜值为69的插入第2个位置之后序列是:77 33 69 51


线段树:倒序插入,这样POS的意义就变为找到这么一个位置可以放置这个人, 使得从这个位置往前数共有POS个空位。
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MAX_N (1<<19)
int dat[MAX_N*2];//线段树节点
int cnt[MAX_N];
int pos[MAX_N];//位置 也表示前面需要多少空位
int seq[MAX_N];//记录结果
//以0为根节点
int e(int x)//返回log2(x-1)
{
	--x;
	int i=0;
	while(x){x/=2;++i;}
	return i;
}
void init_dat(int root,int l,int r)
{//初始化线段树的值,初始值为下辖区间
	dat[root]=r-l+1;
	if(l==r)
		return;
	init_dat(root*2+1,l,(l+r)/2);
	init_dat(root*2+2,(l+r)/2+1,r);
}
void update(int r)
{//向上更新
	while(r)
	{
		--dat[r];
		r=(r-1)/2;
	}
	--dat[r];
}
void insert(int p,int c,int l)
{//为c颜值的寻找位置
	int root=0;
	int limit=l*2;
	while(root*2<limit)
	{//假如左边区间的空位>p 表示需要找的节点在左区间
		//否则,说明在右边,注意往右需要减去左区间的空位数
		if(dat[root*2+1]>p)
			root=root*2+1;
		else {
			p-=dat[root*2+1];
			root=root*2+2;
		}
	}
	seq[root]=c;
	update(root);
}
void solve(int n)
{
	int l=(1<<e(n))-1;//最底层的最左边的叶子节点的编号
	init_dat(0,0,l);
	for(int i=n-1;i>=0;--i)
	{//从后向前逐个寻位
		insert(pos[i],cnt[i],l);
	}
	printf("%d",seq[l]);
	for(int i=1;i<n;++i)
		printf(" %d",seq[l+i]);
	printf("\n");
}
int main()
{
	int n;
	while(~scanf("%d",&n))
	{
		for(int i=0;i<n;++i)
			scanf("%d%d",pos+i,cnt+i);
		solve(n);
	}
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值