并查集 虚拟节点

并查集 虚拟节点

最近学到并查集,遇到了 UVA11987 Almost Union-Find这个经典问题。此时就需要用到虚拟节点这个概念,但是看了很多博客都没怎么理解,今天把自己想法记录一下。

题目描述

在这里插入图片描述

  1. 输入两个元素 p 、 q ,如果 p 、q 不在一个集合中,合并这两个元素所在的集合。
  2. 输入两个元素 p 、 q ,如果 p 、q 不在一个集合中,将 p 添到 q 所在的集合。
  3. 输入一个元素 p ,查询 p 所在的集合的大小和元素和。
    其中,1和3的操作通过并查集都可以很容易的实现,但是并查集不具有2的操作。假如p是一个父节点,那么单纯的f[find(p)] = find(q)会把p的子节点全部添加到q所在的集合中,而实际上我们只需要将p添加到q的集合中。所以我们可以将开辟一个1-2n的数组,在n+1 ~ 2n的空间存储虚拟节点,如下图。
    在这里插入图片描述
    可以用如下代码构造,1-n的节点指向自己的虚拟节点,而虚拟节点指向自己,充当根节点。
for (int i = 1; i <= n; i++) {
	f[i] = i + n, f[i + n] = i + n; 
}

而将1和2合并,我们只需改变其虚拟节点,如图,这样就能使1指向2所在的集合了。
在这里插入图片描述

而,我们需要某个节点单独指向另外一个节点,如下图就不会影响其子节点了。
在这里插入图片描述
代码可以写为f[p] = find(q)

完整代码

#include<bits/stdc++.h>
#define io_opt ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)
#define INF 0x3f3f3f3f
#define rg register
using namespace std;

typedef long long ll;
typedef pair<int, int> PII;
const int N = 2e6 + 10;

int f[N], s[N], sum[N];

int find(int x) {
	return f[x] == x ? x : f[x] = find(f[x]);
}
int n, m;
int main() {
	io_opt;
	while (cin >> n >> m) {
		for (int i = 1; i <= n; i++) {
			f[i] = i + n, f[i + n] = i + n, s[i + n] = 1, sum[i + n] = i;
		}
		for (int i = 1; i <= m; i++) {
			int op, p, q;
			cin >> op;
			if (op == 1) {
				cin >> p >> q;
				int fp = find(p), fq = find(q);
				if (fp != fq) {
					f[fp] = f[fq];
					s[fq] += s[fp];
					sum[fq] += sum[fp];
				}
			}
			else if (op == 2) {
				cin >> p >> q;
				int fp = find(p), fq = find(q);
				s[fp]--, sum[fp] -= p;
				s[fq]++, sum[fq] += p;
				f[p] = fq;
			}
			else {
				cin >> p;
				int fp = find(p);
				cout << s[fp] << " " << sum[fp] << endl;
			}
		}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值