Buy Tickets

Buy Tickets

线段树 / 树状数组上二分 + 逆向思维

SCUACM2022集训前训练-数据结构 - Virtual Judge (vjudge.net)

题意

一开始每个位置都没有人,每次给出一个 \(pos\)\(val\), 把 val 查到第 pos + 1 的位置,求最后每个位置上的值

思路

  1. 因为如果从前向后考虑,前面的人的位置会受后面插队的人的影响,而从后向前插入的话,找到这个该插的位置,之后就不会发生变化了,所以可以从后向前考虑

  2. 可简单模拟一下这个过程:

    若最后一个插到位置 7,则他就在位置 7;

    倒数第二个人插到位置 3,那他就在位置 3;

    倒数第三个人插到位置 5,因为之后会有人插到位置 3,所以这个位置 5 最后会变成位置 6。其实就是找第 5 个空位是哪个位置,这个人最终就在哪个位置,这些空位的实际含义就是:要插到这个位置,那前面的得有这么多人才行,空位就是给他前面的人留的

所以可以用线段树解决,类似权值线段树,比如 n 为 10,插入倒数第三个人的时候

  1. [1, 10] 有 8 个空位,因为这个人肯定在 [1, 10], 所以先给代表这个区间的结点的空位数 --
  2. [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;
}

posted @ 2022-05-24 21:58  hzy0227  阅读(26)  评论(0编辑  收藏  举报