POJ 2828 Buy Tickets (线段树,插队问题)

题目链接:http://poj.org/problem?id=2828


参考博客:http://blog.csdn.net/u013480600/article/details/22091129

题意:n个人排队,他们是按顺序到达的,但是他们乱插队。每个人有两个值pos[i]和val[i]。比如现在第5个人来了,他的pos[5]值为3,那么他就会插队到当前第3个人位置的后面(第0个人是售票窗口)。依次给出所有人的pos和val值,要你最终按所有人的位置顺序输出val值。

对于第一个样例看图就明白了。

输入

4
0 77
1 51
1 33
2 69

输出

77 33 69 51



拿到题之后想了很久,感觉做法不是太好理解,尤其为什么可以转化为线段树维护空位数。看了几个博客后略懂了一些。

线段树,数据逆着插入,最后一个来的人一定位置确定,然后以此类推。这样pos(插在第几个人后面)的意义就变成了,前面有多少个空位。(这句话让我豁然开朗,不过怎样才能想到这样还需要细细考虑)线段树上每个节点中存储的是当前时刻,该区间有多少空位。

知道了做法,还需要注意更新时候的方式,val表示该线段空位置的个数,满足 pos<=T[rt<<1].val(即左儿子的空位多于插入数的位置序号)就访问左儿子,否则访问右儿子
(访问右节点的时候注意pos要修改,改为pos-T[rt].val,即整个线段的第pos个空位,在下一个右儿子那的第pos-T[rt].val个空位)。


下面是从参考博客里粘贴出来的一段解释,感觉说得比较清楚。

我们维护一棵线段树,然后该树的所有叶子节点的初值为1(每个元素的值为1,比如第r元素值为1,说明位置r目前还有1个坑),当我们插入一个人到位置r时候,就把控制区间[r, r]的该叶子节点的sum值置为0(说明当前r位置已经被人占了不能再用了,且这个人永远不会变动位置了。因为我们是从后往前逆向插入的,所以最后一个人的位置始终是固定的)。

       首先最后来到的那个观众肯定是位于pos[n]+1位置的,所以我们用线段树的query操作找到sum==pos[n]+1的那个叶节点,这个叶节点就是我们应该插入的位置,修改该位置的sum值为0,val值为v[n],并更新线段树即可。

       接下来我们要找到n-1观众的位置,假设pos[n-1]+1=x,那么这个x的含义是我们没有插入第n个人的时候整个队列中的第x个空位(即线段树中的第x个1的位置),现在因为已经插入了第n个人,所以我们要自己越过这第n个人的位置,假设他不存在还没有插入。

       所以我们只要找到线段树中的所有叶节点中,sum值正好为1,且正好这个1是所有现存1的叶子节点中的第x个时,那么这个叶子节点就是n-1人应该在的位置。即我们在处理第i个人的时候,自动忽略了那些已经值为0的叶节点,因为他们在第i个人之后,实际上他们在i插入的时候还不存在。


#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
struct node{
	int l, r, m;
	int val, num;
};
const int maxn = 200100;
node T[maxn << 2];
int a[maxn], b[maxn];
void pushup(int rt)  {
	T[rt].val = T[rt << 1].val + T[rt << 1 | 1].val;
}
void build(int begin, int end, int rt) {
	T[rt].l = begin;
	T[rt].r = end;
	T[rt].m = (T[rt].l + T[rt].r) >> 1;
	if(begin == end) {
		T[rt].val = 1;
		return ;
	}
	build(T[rt].l, T[rt].m, rt << 1);
	build(T[rt].m + 1, T[rt].r, rt << 1 | 1);
	pushup(rt);
}
void update(int pos, int num, int rt) {
	if(T[rt].l == T[rt].r) {
		T[rt].val = 0;
		T[rt].num = num;
		return ;
	}
	if(pos <= T[rt << 1].val) update(pos, num, rt << 1);  //更新的关键 
	else update(pos - T[rt << 1].val, num, rt << 1 | 1);
	pushup(rt);
}
void query(int rt) {
	if(T[rt].l == T[rt].r ) {
		printf("%d ", T[rt].num);  //多了个空格也可以过... 
		return ;
	}
	query(rt << 1);
	query(rt << 1 | 1);
}

int main() {
	int n;
	while(~scanf("%d", &n)) {
		build(1, n, 1);
		int pos, num;
		for(int i = 1; i <= n; i++) {
			scanf("%d %d", a + i, b + i);
		}
		for(int i = n; i > 0; i--) {
			update(a[i] + 1, b[i], 1);
		}
		query(1);
		puts("");
	}
	return 0;
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值