UVA11987 Almost Union-Find
阿新 • • 發佈:2021-07-28
題面
題解
維護一種資料結構,支援合併兩個集合,將一個元素轉移到另一個集合,詢問集合的大小和元素和。
不難發現,第一和第三種操作就是普通的並查集就能維護的,只有第二種操作略微有些不同。
如果我們用普通的並查集來維護第二種操作,那麼當被轉移元素為某個集合的代表元素時,轉移顯然是 \(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; }