1063. 永无乡(splay,启发式合并,并查集)

活动 - AcWing

永无乡包含 n 座岛,编号从 1 到 n ,每座岛都有自己的独一无二的重要度,按照重要度可以将这 n 座岛排名,名次用 1 到 n 来表示。

某些岛之间由巨大的桥连接,通过桥可以从一个岛到达另一个岛。

如果从岛 a 出发经过若干座(含 0 座)桥可以到达岛 b ,则称岛 a 和岛 b 是连通的。

现在有两种操作:

  • B x y 表示在岛 x 与岛 y 之间修建一座新桥。
  • Q x k 表示询问当前与岛 x 连通的所有岛中第 k 重要的是哪座岛,即所有与岛 x 连通的岛中重要度排名第 k 小的岛是哪座,请你输出那个岛的编号。
输入格式

第一行是用空格隔开的两个正整数 n 和 m ,分别表示岛的个数以及一开始存在的桥数。

接下来的一行是用空格隔开的 n 个数,依次描述从岛 1 到岛 n 的重要度排名。

随后的 m 行每行是用空格隔开的两个正整数 ai 和 bi ,表示一开始就存在一座连接岛 ai 和岛 bi 的桥。

后面剩下的部分描述操作,该部分的第一行是一个正整数 q ,表示一共有 q 个操作,接下来的 q 行依次描述每个操作,操作的格式如上所述,以大写字母 Q 或 B 开始,后面跟两个不超过 n 的正整数,字母与数字以及两个数字之间用空格隔开。

输出格式

对于每个 Q x k 操作都要依次输出一行,其中包含一个整数,表示所询问岛屿的编号。

如果该岛屿不存在,则输出 −1。

数据范围

对于 20 的数据 n≤1000,q≤1000
对于 100 的数据 n≤100000,m≤n,q≤300000

输入样例:
5 1
4 3 2 5 1
1 2
7
Q 3 2
Q 2 1
B 2 3
B 1 5
Q 2 1
Q 2 4
Q 2 3
输出样例:
-1
2
5
1
2

解析: 

使用并查集来判断连通分量,对于每一个连通分量建一棵splay树,在合并联通分量的时候将splay树进行合并,由于直接合并的时间复杂度很高,因此采用启发式合并:将小的树合并到大的树里,最多合并 nlogn 次,所以启发式合并的部分时间复杂度为 O(nlogn)

#include <iostream>
#include <string>
#include <cstring>
#include <cmath>
#include <ctime>
#include <algorithm>
#include <utility>
#include <stack>
#include <queue>
#include <vector>
#include <set>
#include <math.h>
#include <map>
#include <sstream>
#include <deque>
#include <unordered_map>
#include <unordered_set>
#include <bitset>
#include <stdio.h>
#include <tuple>
using namespace std;
typedef long long LL;
//#define int LL
#define ld long double
const LL INF = 0x3f3f3f3f3f3f3f3f;
typedef unsigned long long ULL;
typedef pair<long long, long long> PLL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
const int inf = 0x3f3f3f3f;
const LL Mod = 998244353;
const ld eps = 1e-12;
const int N = 1800010, M = 5e5 + 10;

int n, m;
struct Node {
	int s[2], v, p, id;
	int size, flg;
	void init(int _v, int _p, int _id) {
		v = _v, p = _p, id = _id;
		size = 1;
	}
}tr[N];
int p[N];
int root[N], idx;
int find(int a) {
	if (p[a] == a)return a;
	return p[a] = find(p[a]);
}
void up(int x) {
	tr[x].size = tr[tr[x].s[0]].size + tr[tr[x].s[1]].size + 1;
}
void rotate(int x) {
	int y = tr[x].p, z = tr[y].p;
	int k = tr[y].s[1] == x;
	tr[z].s[tr[z].s[1] == y] = x, tr[x].p = z;
	tr[y].s[k] = tr[x].s[k ^ 1], tr[tr[x].s[k ^ 1]].p = y;
	tr[x].s[k ^ 1] = y, tr[y].p = x;
	up(y), up(x);
}
void splay(int x, int k,int b) {
	while (tr[x].p != k) {
		int y = tr[x].p, z = tr[y].p;
		if (z != k) {
			if ((tr[y].s[1] == x) ^ (tr[z].s[1] == y))rotate(x);
			else rotate(y);
		}
		rotate(x);
	}
	if (!k)root[b] = x;
}
void insert(int v, int id, int b) {
	int u = root[b], p = 0;
	while (u)p = u, u = tr[u].s[v > tr[u].v];
	u = ++idx;
	if (p)tr[p].s[v > tr[p].v] = u;
	tr[u].init(v, p, id);
	splay(u, 0, b);
}
int get_k(int k, int b) {
	int u = root[b];
	while (u) {
		if (tr[tr[u].s[0]].size >= k)u = tr[u].s[0];
		else if (tr[tr[u].s[0]].size + 1 == k)return tr[u].id;
		else k -= tr[tr[u].s[0]].size + 1, u = tr[u].s[1];
	}
	return -1;
}
void output(int u) {
	if (tr[u].s[0])output(tr[u].s[0]);
	cout << "______" << tr[u].v << " " << tr[u].id << endl;
	if (tr[u].s[1])output(tr[u].s[1]);
}
void dfs(int u, int b) {
	if (tr[u].s[0])dfs(tr[u].s[0], b);
	if (tr[u].s[1])dfs(tr[u].s[1], b);
	insert(tr[u].v, tr[u].id, b);
}
signed main() {
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		int a;
		scanf("%d", &a);
		p[i]=root[i] = i;
		tr[i].init(a, 0, i);
	}
	idx = n;
	for (int i = 1; i <= m; i++) {
		int a, b;
		scanf("%d%d", &a, &b);
		int pa = find(a), pb = find(b);	
		if (pa != pb) {
			if (tr[root[pa]].size > tr[root[pb]].size)swap(pa, pb);
			dfs(root[pa], pb);
			p[pa] = pb;
		}
	}

	//for (int i = 1; i <= n; i++) {
	//	output(root[i]);
	//	cout << endl;
	//	cout << endl;
	//}

	int Q;
	cin >> Q;
	char op[2];
	int a, b;
	while (Q--) {
		scanf("%s%d%d", op, &a, &b);
		if (op[0] == 'Q') {
			int pa = find(a);
			if (tr[root[pa]].size < b)printf("-1\n");
			else printf("%d\n", get_k(b, pa));
			//output(root[pa]);
		}
		else {
			int pa = find(a), pb = find(b);
			if (pa != pb) {
				if (tr[root[pa]].size > tr[root[pb]].size)swap(pa, pb);
				dfs(root[pa], pb);
				p[pa] = pb;
			}
		}
	}
	return 0;
}

/*
5 1
4 3 2 5 1
1 2
7
Q 3 2
Q 2 1
B 2 3
B 1 5
Q 2 1
Q 2 4
Q 2 3

-1
2
5
1
2
*/

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值