UVA11987 Almost Union-Find
题面
题解
维护一种数据结构,支持合并两个集合,将一个元素转移到另一个集合,询问集合的大小和元素和。
不难发现,第一和第三种操作就是普通的并查集就能维护的,只有第二种操作略微有些不同。
如果我们用普通的并查集来维护第二种操作,那么当被转移元素为某个集合的代表元素时,转移显然是 \(O(n)\) 的,而当我们路径压缩以后,如果被转移元素不是集合的代表元素时,转移时 \(O(1)\) 的,因为它们直接连到了根节点,只用替换被转移元素连接的父节点就可以了,但如果是代表元素,那么集合内剩下的元素还要重新找一个代表元素。
所以我们考虑优化转移代表元素的情况。
可以发现,它主要的时间是花在了集合剩余的元素的代表元素重选,那么我们就考虑不重选代表元素。
我们设置虚点表示集合的代表元素,最开始时,每个元素连向它对应的虚点,合并集合就直接合并集合对应的虚点,在虚点记录集合的大小及元素和,转移元素时,因为每个数都不是集合的代表元素,所以直接把被转移的数的父节点设为转移集合的代表虚点就好了。
注意多组数据
代码
#include<cstdio>
using namespace std;
typedef long long LL;
const int N = 1e5 + 5;
int fa[N << 1], siz[N], n, m; LL val[N];
int Find(int x) { return x == fa[x] ? x : fa[x] = Find(fa[x]); }
int main() {
while(~scanf("%d%d", &n, &m)) {
for(int i = 1; i <= n; i++) fa[i] = fa[n + i] = val[i] = i, siz[i] = 1;
for(int i = 1, opr; i <= m; i++) {
scanf("%d", &opr);
if(opr == 1) {
int p, q;
scanf("%d%d", &p, &q);
p = Find(p + n), q = Find(q + n);
if(p == q) continue;
fa[q] = p; siz[p] += siz[q]; val[p] += val[q];
}
else if(opr == 2) {
int p, q;
scanf("%d%d", &p, &q);
if(Find(p + n) == Find(q + n)) continue;
siz[Find(p + n)]--; val[Find(p + n)] -= p;
siz[fa[p + n] = Find(q + n)]++; val[fa[p + n]] += p;
}
else {
int p;
scanf("%d", &p);
printf("%d %lld\n", siz[Find(p + n)], val[Find(p + n)]);
}
}
}
return 0;
}

浙公网安备 33010602011771号