[题解]LuoGu3224:[HNOI2012]永无乡

原题传送门
这个貌似叫 S p l a y Splay Splay d s u dsu dsu
首先,这是个跟联通块有关的问题,所以需要用上并查集

然后,联通块的第k大当然是用 S p l a y Splay Splay维护了
可以对于每个点,都建一棵 S p l a y Splay Splay,对(1~n)点用并查集维护联通
点内用Splay维护 k t h kth kth

值得一提的是本题需要用到 S p l a y Splay Splay的合并,做法是将size小的splay一个点一个点插到size大的splay里面去
插入顺序则不用去管它,不需要中序遍历

注意:数组范围开大,我开了500010,因为不止有一棵 S p l a y Splay Splay

Code:

#include <bits/stdc++.h>
#define maxn 500010
using namespace std;
int sz, ff[maxn], rt[maxn], n, m, f[maxn];
int size[maxn], son[maxn][2], key[maxn], id[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;
}

int get(int x){ return son[f[x]][1] == x; }

int getf(int k){ return k == ff[k] ? k : ff[k] = getf(ff[k]); }

void pushup(int x){
	if (x > n){
		size[x] = 1;
		if (son[x][0] > n) size[x] += size[son[x][0]];
		if (son[x][1] > n) size[x] += size[son[x][1]];
	}
}

void connect(int x, int y, int z){
	if (x > n) f[x] = y;
	if (y > n) 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 <= n) rt[goal] = x;
}

void insert(int x, int u){
	int now = rt[u], fa = 0;
	while (now && key[now] != x) fa = now, now = son[now][x > key[now]];
	now = ++sz;
	key[now] = x, size[now] = 1, son[now][0] = son[now][1] = 0;
	f[now] = fa;
	if (fa > n) son[fa][x > key[fa]] = now;
	pushup(fa); splay(now, u);
}

void dfs(int now, int u){
	if (son[now][0]) dfs(son[now][0], u);
	if (son[now][1]) dfs(son[now][1], u);
	insert(key[now], u);
}

void merge(int x, int y){
	x = getf(x), y = getf(y);
	if (x == y) return;
	if (size[rt[x]] > size[rt[y]]) swap(x, y);
	ff[x] = y;
	dfs(rt[x], y);
}

int kth(int u, int x){
	int now = rt[getf(u)];
	if (size[now] < x) return -1;
	while (1){
		if (size[son[now][0]] >= x) now = son[now][0]; else
		if (size[son[now][0]] + 1 >= x) return key[now]; else
		x -= size[son[now][0]] + 1, now = son[now][1];
	}
}

int main(){
	n = read(), m = read(), sz = n << 1;
	for (int i = 1; i <= n; ++i){
		int x = read();
		id[x] = i, rt[i] = n + i;
		size[n + i] = 1, key[n + i] = x, f[n + i] = i, ff[i] = i;
		son[n + i][0] = son[n + i][1] = 0;
	}
	for (int i = 1; i <= m; ++i){
		int x=  read(), y = read();
		merge(x, y);
	}
	m = read();
	while (m--){
		char c = getchar();
		for (; c != 'B' && c != 'Q'; c = getchar());
		int x = read(), y = read();
		if (c == 'B') merge(x, y); else{
			int ans = kth(x, y);
			printf("%d\n", ans == -1 ? -1 : id[ans]);
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值