并查集 虚拟节点
最近学到并查集,遇到了 UVA11987 Almost Union-Find这个经典问题。此时就需要用到虚拟节点这个概念,但是看了很多博客都没怎么理解,今天把自己想法记录一下。
题目描述
- 输入两个元素 p 、 q ,如果 p 、q 不在一个集合中,合并这两个元素所在的集合。
- 输入两个元素 p 、 q ,如果 p 、q 不在一个集合中,将 p 添到 q 所在的集合。
- 输入一个元素 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;
}