UVA11987 Almost Union-Find

题面

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;
}
posted @ 2021-07-28 21:34  init-神眷の樱花  阅读(41)  评论(0)    收藏  举报