POJ - 2828 Buy Tickets
题目
假设有 n 个人要排队,刚开始没有人,现在每次给你 n 个人的两个属性 p,v,代表这个人插在了第 p 个人的后面,这个人的权值是v。最后输出排队序列。
分析
题目意思很好懂,但是很容易超时,刚开始我看到有很多插入操作,想用单链表模拟,但是找到插到第几个人前面也只能遍历,复杂度依然是 n 2 n^2 n2。
换一种思路,既然前面给你的人的位置都有可能被人插队,那么就从后面遍历,后面的人不会被插队。因此位置直接确定。
每找到一个人的位置这个位置就置不空,找位置的时候只在非空的位置找。
简单说就是:找第p+1空位
相应的线段树维护区间的空位置数量。
#include <cstdio>
#include <iostream>
#include <cstring>
#include <cmath>
#include <algorithm>
#define INF 0x3f3f3f3f
#define d(x) cout << (x) << endl
#define lson l, m, rt<<1
#define rson m+1, r, rt<<1|1
// #include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod = 9999991;
const int N = 2e5 + 10;
int n;
struct node{
int p, v;
} a[N];
int tr[N << 2]; //线段树维护区间代表空位置
int num[N]; //存最后的排队顺序
void build(int l, int r, int rt){
tr[rt] = r - l + 1; //初始空位置为区间长度
if(l == r)
return;
int m = l + r >> 1;
build(lson);
build(rson);
}
void update(int l, int r, int rt, int pos, int ans){
tr[rt]--; //填入一个数,相应的这个区间的空位置减一
if(l == r){ //只会找到一个端点,就是要填入的位置
num[l] = ans;
return;
}
int m = l + r >> 1;
if(pos <= tr[rt<<1]){ //如果位置在左边,递归左儿子
update(lson, pos, ans);
}else{ //在右边的话,递归右儿子
pos -= tr[rt<<1]; //减去左区间长度,pos就变成在右区间的长度了
update(rson, pos, ans);
}
}
int main()
{
while(~scanf("%d", &n)){
build(1, n, 1);
for (int i = 1; i <= n; i++){
scanf("%d%d", &a[i].p, &a[i].v);
}
for(int i = n; i > 0; i--){
update(1, n, 1, a[i].p + 1, a[i].v);
}
for(int i = 1; i <= n; i++){
printf("%d%c", num[i], " \n"[i == n]);
}
}
return 0;
}