知识点:并查集
这个题算是并查集第四个知识点,前面三个分别是边带权,扩展域,离线,这个知识点应该是并查集的删除,这个很明显要用并查集来做,难点很明显是第二个操作,一般的并查集我们不好删除,因为一个结点它不一定就是叶子结点,如果是叶子结点,那么说删就删了,但是不是叶子结点的结点,它的下面还连着一些结点,这个不好处理,所以我们采取办法使所有的结点一直都是叶子结点,那么就是建立一些辅助的点,有的地方说是虚点,有的说是副本,感觉怎么说都可以,我们建立辅助点,每个辅助点下面都连接一个实际的题目里面的节点,那么很显然我们的空间要开原来的两倍,接下来,合并某两点所在的集合,就根平常的一样,让一个集合的根连在另一个集合的根下面,删除某个点,那么我们就是直接让这个点父亲是它自己,等等,这样我们会发现,不论是合并,删除,等等操作,我们的并查集的叶子结点始终是我们要处理的实际的信息,根节点,中间层结点都是一开始设置的辅助点,除了某些被删除的元素自成一个连通块,也就是可能某个点已经不是连在一开始设置的辅助结点下面了,但是它有父亲的话,父亲必定是辅助点,这样,第二个操作的核心,删除一个并查集的元素就完成了,剩下的元素插入某个集合,更新信息就很简单了
总结一下,并查集删除结点的办法就是一开始把所有有点都连接在辅助点的下面,然后不论是合并还是删除等操作,都能把所有实际点保持在叶子结点的位置,也就方便了删除这个操作,所以需要开2倍的空间,看起来像扩展域并查集但是逻辑上完全不是
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int fa[N * 2], d[N * 2];
long long sum[N * 2];
int get(int x) {
if (x == fa[x]) return x;
return fa[x] = get(fa[x]);
}
void merge1(int x, int y) {
int fx = get(x), fy = get(y);
fa[fx] = fy;
d[fy] += d[fx];
sum[fy] += sum[fx];
}
void merge2(int x, int y) {
int fx = get(x), fy = get(y);
fa[x] = fy;
d[fx]--;
d[fy]++;
sum[fx] -= (long long) x;
sum[fy] += (long long) x;
}
int main() {
int n, m;
while (cin >> n >> m) {
for (int i = 1; i <= n; i++) {
fa[i] = fa[i + n] = i + n;
d[i + n] = 1;
sum[i + n] = i;
}
while (m--) {
int op, x, y;
cin >> op;
if (op == 1) {
cin >> x >> y;
if (get(x) == get(y)) continue;
merge1(x, y);
} else if (op == 2) {
cin >> x >> y;
if (get(x) == get(y)) continue;
merge2(x, y);
} else {
cin >> x;
cout << d[get(x)] << ' ' << sum[get(x)] << '\n';
}
}
}
return 0;
}