Almost Union-Find
支持三种操作
- 合并 x x x和 y y y所在的集合
- 把 x x x移到 y y y所在的集合
- 求 x x x所在的集合的元素个数和元素之和
操作1和3是基本的并查集的操作.
关键在于操作
2
2
2:
若使用朴素的并查集,把节点
1
1
1合并到
3
3
3所在的集合,会同时也把1的儿子节点
2
2
2也移动过去,这不是我们想要的。(这里借用luogu题解区的图片)
我们可以通过给每个元素设置一个虚拟父节点,每次移动的时候操作的是实际的节点,而不是父节点即可完成。因为这个时候操作的节点都是叶子节点,不会有副作用。
代码中的下标从 0 0 0开始,因此在输入输出的时候有特殊处理。操作2对应于 D S U DSU DSU的 m o v e move move函数.
#include <bits/stdc++.h>
using namespace std;
#ifdef LOCAL
#include "debug.h"
#else
#define debug(...) 42
#endif
typedef long long LL;
struct DSU {
std::vector<int> f, siz;
// 集合内的元素和
std::vector<long long> s;
DSU() {}
DSU(int n) {
init(n);
}
void init(int n) {
f.resize(n * 2);
siz.resize(n * 2);
s.resize(n * 2);
// 每个点有一个虚点,对应的编号为: i+n, 虚点的父亲指向自己
for (int i = n; i < n + n; i++) {
f[i - n] = i;
f[i] = i;
siz[i] = 1;
s[i] = i - n;
}
}
int find(int x) {
while (x != f[x]) {
x = f[x] = f[f[x]];
}
return x;
}
bool same(int x, int y) {
return find(x) == find(y);
}
bool merge(int x, int y) {
x = find(x);
y = find(y);
if (x == y) {
return false;
}
siz[x] += siz[y];
s[x] += s[y];
f[y] = x;
return true;
}
// 把y添加到x的集合
bool move(int x, int y) {
int u = find(x), v = find(y);
if (u == v) {
return false;
}
// 注意这里的变量,y是叶子节点
f[y] = u;
siz[u]++, siz[v]--;
s[u] += y, s[v] -= y;
return true;
}
int size(int x) {
return siz[find(x)];
}
int sum(int x) {
return s[find(x)];
}
};
int main() {
int n, m;
while (cin >> n >> m)
{
DSU dsu(n);
while (m--) {
int op, p, q;
cin >> op;
if (op == 1) {
cin >> p >> q;
dsu.merge(p - 1, q - 1);
} else if (op == 2) {
cin >> p >> q;
dsu.move(q - 1, p - 1);
} else {
cin >> p;
cout << dsu.size(p - 1) << " " << dsu.sum(p - 1) + dsu.size(p - 1) << endl;
}
}
}
return 0;
}