[题解]LuoGu:3690:列队

博客围绕一道题给出两种解法。法1用Splay,每行建一棵Splay维护kth,最后一列也建一棵,原做法空间复杂度为O(n2),可将区间合并成点优化空间;法2用权值线段树,开n+1个线段树分别维护最后一列和n行,动态维护队列情况。

原题传送门

法1 Splay

题意:对于每个 ( x , y ) (x,y) (x,y),把第x行第y个踢掉,把第x行最后一个移到第x行里面,把踢掉的数插到最后一列的最后一个

十分容易想到一个思路:1到n行每行建一棵splay,维护1~m-1的kth,再按最后一列建一棵 s p l a y splay splay,维护最后一列的kth
那么做法显而易见:

  • 把最后一列的 k t h ( x ) kth(x) kth(x)插到第x行的末尾
  • 找到第x行的 k t h ( y ) kth(y) kth(y),踢掉并输出
  • 把踢掉的数插到最后一列末尾

分析一下复杂度,发现空间是 O ( n 2 ) O(n^2) O(n2)

想优化

可以把一段区间合并成一个点
每个点代表一个区间,初始时每行都只有一个点,代表区间便为 [ ( i − 1 ) ∗ m + 1 , i ∗ m ) [(i-1)*m+1,i*m) [(i1)m+1,im),注意是左闭右开
最后一列的话只能一个一个插进去了

每次踢出一个数,把包含这个数的点,分成三个(也有可能不到三个),假如区间是 [ l , r ) [l,r) [l,r),那么把区间分裂成 [ l , k ) , k , [ k + 1 , r ) [l,k) ,{k},[k+1, r) [l,k),k,[k+1,r),然后把k的这个点删掉

这样就把空间优化了

Code:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#define maxn 3000010
#define LL long long
using namespace std;
int n, m, M, sz;
int size[maxn], son[maxn][2], f[maxn];
LL l[maxn], r[maxn];

inline int read(){
	int s = 0, w = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') w = -1;
	for (; isdigit(c); c = getchar()) s = (s << 1) + (s << 3) + (c ^ 48);
	return s * w;
}

struct Splay{
	int rt;
	int addnode(LL ll, LL rr){
		++sz;
		l[sz] = ll, r[sz] = rr, size[sz] = r[sz] - l[sz];
		f[sz] = son[sz][0] = son[sz][1] = 0;
		return sz;
	}
	
	void init(LL ll, LL rr){
		rt = addnode(ll, rr);
	}
	
	int get(int x){ return son[f[x]][1] == x; }
	
	void pushup(int x){ size[x] = size[son[x][0]] + size[son[x][1]] + r[x] - l[x]; }
	
	void connect(int x, int y, int z){
		f[x] = y, son[y][z] = x;
	}
	
	void rotate(int x){
		int fa = f[x], ffa = f[fa], m = get(x), n = get(fa);
		connect(son[x][m ^ 1], fa, m);
		connect(fa, x, m ^ 1);
		connect(x, ffa, n);
		pushup(fa); pushup(x);
	}
	
	void splay(int x, int goal){
		while (f[x] != goal){
			int fa = f[x];
			if (f[fa] != goal) rotate(get(x) == get(fa) ? fa : x);
			rotate(x);
		}
		if (!goal) rt = x;
	}
	
	int split(int x, LL k){
		k += l[x];
		int o = addnode(k, r[x]);
		r[x] = k;
		if (!son[x][1]) connect(o, x, 1); else{
			int now = son[x][1];
			while (son[now][0]) now = son[now][0];
			connect(o, now, 0);
			while (now != x) pushup(now), now = f[now];
		}
		splay(o, 0);
		return o;
	}
	
	LL popkth(int k){
		int now = rt;
		while (1){
			if (size[son[now][0]] >= k) now = son[now][0]; else{
				k -= size[son[now][0]];
				if (k <= r[now] - l[now]){
					if (k < r[now] - l[now]) split(now, k);
					if (k > 1) now = split(now, k - 1);
					break;
				}
				k -= r[now] - l[now], now = son[now][1];
			}
		}
		splay(now, 0);
		f[son[now][0]] = f[son[now][1]] = 0;
		if (!son[now][0]) rt = son[now][1]; else
		if (!son[now][1]) rt = son[now][0]; else{
			int o = son[now][0];
			while (son[o][1]) o = son[o][1];
			splay(o, 0);
			connect(son[now][1], o, 1);
			pushup(o);
		}
		return l[now];
	}
	
	void insert(LL x){
		int o = addnode(x, x + 1);
		if (!rt) rt = addnode(x, x + 1); else{
			int now = rt;
			while (son[now][1]) now = son[now][1];
			connect(o, now, 1);
			pushup(o); splay(o, 0);
		}
	}
}s[maxn];

int main(){
	n = read(), m = read(), M = read();
	for (LL i = 1; i <= n; ++i) s[i].init((i - 1) * m + 1, i * m);
	s[0].init(m, m + 1);
	for (LL i = 2; i <= n; ++i) s[0].insert(i * m);
	while (M--){
		int x = read(), y = read();
		s[x].insert(s[0].popkth(x));
		LL ans = s[x].popkth(y);
		printf("%lld\n", ans);
		s[0].insert(ans);
	}
	return 0;
}

法2 线段树

这里用到的是权值线段树
因为要解决空间问题,所以可以用权值线段树动态维护队列的情况
n + 1 n+1 n+1个线段树分别维护最后一列和去掉最后一列的 n n n
每次暴力取出答案,在最后一列的末尾加上
取出最后一列对应行的那个数,在那一行的末尾加上

Code:

#include <bits/stdc++.h>
#define maxn 1000010
#define maxm 10000010
#define LL long long
using namespace std;
struct Seg{
	int size;
	LL val;
}seg[maxm];
int ls[maxm], rs[maxm], n, m, Q, cnt[maxn], now, rt[maxn], lim, size;

inline int read(){
	int s = 0, w = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') w = -1;
	for (; isdigit(c); c = getchar()) s = (s << 1) + (s << 3) + (c ^ 48);
	return s * w;
}

int getsize(int l, int r){ return now > n ? (l <= n ? (r <= n ? r - l + 1 : n - l + 1) : 0) : (l < m ? (r < m ? r - l + 1 : m - l) : 0); }

LL query(int &id, int x, int l, int r){
	if (!id){
		seg[id = ++size].size = getsize(l, r);
		if (l == r)
			if (now <= n) seg[id].val = 1LL * (now - 1) * m + 1LL * l; 
			else seg[id].val = 1LL * l * m;
	}
	--seg[id].size;
	int mid = (l + r) >> 1;
	if (l == r) return seg[id].val;
	if (!ls[id] && x <= mid - l + 1 || x <= seg[ls[id]].size) return query(ls[id], x, l, mid);
	else return query(rs[id], ls[id] ? x - seg[ls[id]].size : x - (mid - l + 1), mid + 1, r);
}

void update(int &id, int x, LL y, int l, int r){
	if (!id){
		seg[id = ++size].size = getsize(l, r);
		if (l == r) seg[id].val = y;
	}
	++seg[id].size;
	if (l == r) return;
	int mid = (l + r) >> 1;
	if (x <= mid) update(ls[id], x, y, l, mid);
	else update(rs[id], x, y, mid + 1, r);
}

int main(){
	n = read(), m = read(), Q = read(), lim = max(n, m) + Q;
	while (Q--){
		int x = read(), y = read();
		LL z;
		now = y < m ? x : n + 1;
		printf("%lld\n", z = query(rt[now], y < m ? y : x, 1, lim));
		++cnt[now = n + 1];
		update(rt[now], n + cnt[now], z, 1, lim);
		if (y < m){
			z = query(rt[now], x, 1, lim);
			++cnt[x];
			update(rt[now = x], m - 1 + cnt[x], z, 1, lim);
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值