LOJ 2319「NOIP2017」列队 线段树

题面

题目传送门

解法

想了好久才把这道题真正弄懂……
可能还是我太菜了

  • 这道题做法比较多,这里讲一个线段树的做法
  • 考虑建出 n + 1 n+1 n+1棵线段树,前 n n n棵线段树维护每一行的情况,最后一棵线段树维护最后一列的情况
  • 显然这些线段树一定要动态开点,否则空间肯定爆炸
  • 口胡一下如何处理离队事件,假设在 ( x , y ) (x,y) (x,y)的同学离队:
  • 如果 y = m y=m y=m,即这个同学在最后一列,显然只有最后一列才会出现变动。那么就在最后一棵线段树上把第 y y y个位置上的数删掉,然后将这个数放到最后一个去。如果 y &lt; m y&lt;m y<m,即这个同学不在最后一列,那么先在第 x x x棵线段树上找到第 y y y个位置,把这个位置删掉,然后在最后一棵线段树上找到第 x x x个点并把这个点插入到第 x x x棵线段树的最后,然后在末尾插入原来被删除的数
  • 然后细想想如何实现,发现怎么删除一个数并且知道第 k k k个没有被删除的数是什么并没有那么好做
  • 其实我们并不需要在删除一个数的时候将它后面的数全部向前平移一格,可以直接在末尾插入,然后找到第 K K K个没有被删除的位置就可以了
  • 如果线段树的每一个节点记这个区间还有多少个没有被删除,显然比较难实现
  • 那么,我们不妨记这一段区间有多少个被删除的点,那么在求解第 K K K个没有被删除的位置就非常好求了,直接在线段树上二分即可
  • 假设当前我们在线段树上找到的点的位置为 x x x,以前 n n n棵线段树为例(其实最后一棵线段树是类似的),如果 x &lt; m x&lt;m x<m,那么可以发现这一个点一定没有被删除过,所以就是原来的位置,可以直接计算得出。否则这个点一定是由其他位置插入进来的点,那么我们对于这一行开一个vector,在插入一个数的时候直接将这个数插入vector中,然后这时对应的人的编号即为 v [ x − m ] v[x-m] v[xm]
  • 因为过程可能较为抽象,所以建议配合代码一起理解
  • 如果有不太理解的地方直接在下方评论即可
  • 时间复杂度: O ( q log ⁡ n ) O(q\log n) O(qlogn)

【注意事项】

  • 因为线段树为动态开点,所以值域一定要开大,开到 [ 1 , m a x ( n , m ) + q ] [1,max(n,m)+q] [1,max(n,m)+q]是足够的,否则可能会出现查不到的情况

代码

#include <bits/stdc++.h>
#define ll long long
#define N 300010
using namespace std;
template <typename node> void read(node &x) {
	x = 0; int f = 1; char c = getchar();
	while (!isdigit(c)) {if (c == '-') f = -1; c = getchar();}
	while (isdigit(c)) x = x * 10 + c - '0', c = getchar(); x *= f;
}
struct Node {
	int lc, rc, cnt;
} t[N * 21];
int tot, rt[N];
vector <ll> v[N];
void ins(int &k, int l, int r, int x) {
	if (!k) k = ++tot; t[k].cnt++;
	if (l == r) return; int mid = (l + r) >> 1;
	if (x <= mid) ins(t[k].lc, l, mid, x);
		else ins(t[k].rc, mid + 1, r, x);
}
int query(int k, int l, int r, int x) {
	if (l == r) return l;
	int mid = (l + r) >> 1, tmp = (mid - l + 1) - t[t[k].lc].cnt;
	if (x <= tmp) return query(t[k].lc, l, mid, x);
	return query(t[k].rc, mid + 1, r, x - tmp);
}
int main() {
	int n, m, q; read(n), read(m), read(q);
	int mx = max(n, m) + q;
	while (q--) {
		int x, y; read(x), read(y);
		if (y == m) {
			int tmp = query(rt[n + 1], 1, mx, x); ll ans;
			if (tmp <= n) ans = 1ll * tmp * m; else ans = v[n + 1][tmp - n - 1];
			cout << ans << "\n";
			ins(rt[n + 1], 1, mx, tmp), v[n + 1].push_back(ans);
		} else {
			int tmp = query(rt[x], 1, mx, y); ll ans;
			if (tmp <= m - 1) ans = 1ll * (x - 1) * m + tmp;
				else ans = v[x][tmp - m];
			cout << ans << "\n";
			int pos = query(rt[n + 1], 1, mx, x); ll tx;
			if (pos <= n) tx = 1ll * pos * m; else tx = v[n + 1][pos - n - 1];
			ins(rt[x], 1, mx, tmp), ins(rt[n + 1], 1, mx, pos);
			v[x].push_back(tx), v[n + 1].push_back(ans);
		}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值