poj-2828 Buy Tickets

考点

  • 线段树

题解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <cstring>
#include <iostream>
using namespace std;
#define lc (p << 1)
#define rc (p << 1 | 1)
const int maxn = 2e5 + 50;
int n, op[maxn], val[maxn];
int a[maxn], s[maxn << 2];

void build(int p, int l, int r) {
if (l == r) {
s[p] = 1;
return;
}
int mid = (l + r) >> 1;
build(lc, l, mid), build(rc, mid + 1, r);
s[p] = s[lc] + s[rc];
}

void update(int p, int l, int r, int x, int v) {
if (l == r) {
a[l] = v;
s[p] = 0;
return;
}
int mid = (l + r) >> 1;
if (s[lc] >= x)
update(lc, l, mid, x, v);
else
update(rc, mid + 1, r, x - s[lc], v);
s[p] = s[lc] + s[rc];
}

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

思路

vector可以直接过,不再诠释;这里展示一个线段树二分的做法。

正着弄,每次都会修改后续区间,非常麻烦,所以尝试反着弄。

根据题意,越后插入的,越在前面,并且只会插在已出现的人之前(没人你怎么插)。

比如有(1, 1), (1, 2), (2, 3), (1, 4)的插入顺序,最终结果就是4 2 3 1

记录每个区间的空闲格子数,用线段树进行如下二分,假设当前要在第x个位置插入:

  1. 如果左孩子的空闲格子数大于等于x,就走向左孩子
  2. 否则就走向右孩子

道理很简单,因为从后往前处理,插入位置的值就是最终结果;但是,会被其他人挤下去。

比如(1, 1), (1, 2), (2, 3), (1, 4)这个执行顺序,从后往前处理,

显然2位置会被1位置挤一次,因为12小,但是竟然出现在了2的后面,显然真正的1位置是有数字的。

如果改成.....(2, 3), (3, 4)就不会有问题,因为3位置比2大,挤不到。

所以本次线段树二分,实际上就是找一个x不会被挤的区间,这样的位置才是最终位置。