GDUT_专题五:H - Buy Tickets 【线段树,单点修改】

题目传送门

题意:

输入n,表示插队的人数(一开始没有有排队)0号位置为售票处

接下来n行分别输入一个pos,val,表示插队的位置和插队的人的标号。插队的位置只能在已排队的人的位置插队。

求最终的队伍的排列顺序。

思路分析:

每次插队后,都插在插队位置的后面,那么位置后面的人都需要后退一个空位,才能成功插入,显然这样维护起来十分麻烦。所以我们不如倒过来排,对于先排的人,他的位置便不会再改变,对于后排的人,如果已经有人排了,那么就只能寻找后面的空位。

那为了方便找到想排的位置已经被占的情况下,后面的空位,对此我们可以选择无视已经在队伍中的人,那么对于我们最终插入的位置x+1,那么前面一定就有x个空位

举个栗子:

对于插入顺序为:

3

0 1

0 3

1 2

原始的队伍中是没有人的,0 0 0

倒序插入 ,首先是1 2,插入后队伍为 0 2 0

然后是0 3 ,插入后 3 2 0

最后0 1,插入后为 3 2 1 (这里因为原来的位置已经有人了,那么我们无视掉人的话,只有位置3的前面有0个空位,所以插入位置3。

所以我们为线段树的每个子叶标记上空位1。更新时,若排入子叶后将其标记清除为0,表示无空位。对于每个子叶单点维护后,最终查询即可。

代码:

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<math.h>
#include<vector>
#include<queue>
#define INF 0x3f3f3f
#define ll long long
#define fre() freopen(".in", "r", stdin);freopen(".out", "w", stdout);
using namespace std;
const double eps=1e-6;
const int N=2e5+10;
const int mod=1e9+7;
int n; 
int pos[N],val[N],ans[N];
struct tree{
	int l,r,sum;
	int mid(){
		return l+r>>1;
	}
}t[N<<2];

inline void update(int node){//更新空位数
	t[node].sum=t[node<<1].sum+t[node<<1|1].sum;
}
void build(int node,int l,int r){
	t[node].l=l;t[node].r=r; 
	if(l==r){
		t[node].sum=1;//标记空位
		return ;
	}
	int mid=t[node].mid();
	build(node<<1,l,mid);
	build(node<<1|1,mid+1,r);
	update(node);
}
//单点修改 
void change(int node,int x,int y){
	if(t[node].l==t[node].r){
		t[node].sum=0;//标记这个点被插队 
		ans[t[node].l]=y;//记录下该位置的人 
		return ;
	}
	if(x<=t[node<<1].sum) change(node<<1,x,y);//如果想插入的位置小于子节点的可插空数 
	else change(node<<1|1,x-t[node<<1].sum,y); //否则需要减去左结点能插空的人数,得到相对右结点的插空位置 
	update(node);
}

int main() {
	while(~scanf("%d",&n)){
		memset(ans,0,sizeof(ans));
		for(int i=1;i<=n;i++){
			scanf("%d%d",&pos[i],&val[i]);
		}
		build(1,1,n);
	//	for(int i=1;i<=n<<1;i++)printf("t[%d] [%d,%d] sum=%d \n",i,t[i].l,t[i].r,t[i].sum);
		for(int i=n;i>=1;i--){//因为后面插完队后不会被前面的影响,所以逆序改变 
			change(1,pos[i]+1,val[i]);
		}
		for(int i=1;i<=n;i++) printf("%d ",ans[i]);
		printf("\n");
	}
	return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值