1. 程式人生 > 其它 >UVA11987 Almost Union-Find

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;
}