Buy Tickets
Buy Tickets
线段树 / 树状数组上二分 + 逆向思维
SCUACM2022集训前训练-数据结构 - Virtual Judge (vjudge.net) 、
题意
一开始每个位置都没有人,每次给出一个 \(pos\) 和 \(val\), 把 val 查到第 pos + 1 的位置,求最后每个位置上的值
思路
-
因为如果从前向后考虑,前面的人的位置会受后面插队的人的影响,而从后向前插入的话,找到这个该插的位置,之后就不会发生变化了,所以可以从后向前考虑
-
可简单模拟一下这个过程:
若最后一个插到位置 7,则他就在位置 7;
倒数第二个人插到位置 3,那他就在位置 3;
倒数第三个人插到位置 5,因为之后会有人插到位置 3,所以这个位置 5 最后会变成位置 6。其实就是找第 5 个空位是哪个位置,这个人最终就在哪个位置,这些空位的实际含义就是:要插到这个位置,那前面的得有这么多人才行,空位就是给他前面的人留的
所以可以用线段树解决,类似权值线段树,比如 n 为 10,插入倒数第三个人的时候
- [1, 10] 有 8 个空位,因为这个人肯定在 [1, 10], 所以先给代表这个区间的结点的空位数 --
- [1, 5] 有 4 个空位,所以不在 [1, 5], 那就让空位数 5 -= 4, 就是在右儿子 [6, 10] 上找第 1 个空位
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#include <cmath>
#include <cstdio>
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
typedef pair<int, int> PII;
int n, idx;
PII q[N];
int ans[N];
struct Node
{
int l, r;
int sum;//这个结点有多少空位
}tr[N * 4];
void pushup(int u)
{
tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}
void build(int u, int l, int r)
{
tr[u] = {l, r};
if (l == r)
{
tr[u].sum = 1;
return;
}
int mid = l + r >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
pushup(u);
}
void modify(int u, int l, int r, int p)
{
//搜到的结点说明这个人的最终位置在这个结点,那这个结点的空位--
tr[u].sum--;
if (l == r)
{
idx = l;
return;
}
int mid = l + r >> 1;
//这个人的位置在左儿子
if (p <= tr[u << 1].sum)
modify(u << 1, l, mid, p);
else
{
p -= tr[u << 1].sum;
modify(u << 1 | 1, mid + 1, r, p);
}
}
int main()
{
while(scanf("%d", &n) != EOF)
{
for (int i = 1; i <= n; i++)
scanf("%d%d", &q[i].first, &q[i].second);
build(1, 1, n);
//倒着枚举,从后往前插队的话,这个人的位置可以固定
for (int i = n; i >= 1; i--)
{
//找到第 q[i].first + 1 个空位给这个人
modify(1, 1, n, q[i].first + 1);
ans[idx] = q[i].second;
}
for (int i = 1; i <= n; i++)
printf("%d ", ans[i]);
puts("");
}
return 0;
}