--- tags: 109_FDCS --- # Disjoint Set Union (DSU) / 互斥集 DSU是用來快速判斷兩個點是否屬於同一個集合的資料結構 資料結構的模型是很多棵樹,如果兩個點的樹根不同則兩個點不是屬於同一棵樹 藉此來判斷是否屬於同一個集合 ## init 對於每一個點,DSU要維護兩個值 1. 子樹大小 2. 父節點 ```cpp=1 vector<int> anc(n), sz(n, 1); iota(anc.begin(), anc.end(), 0); ``` 而一開始所有點的子樹大小都為 $1$ 父節點都為自己 **只要父節點為自己,則此節點為樹根** ## find 用來找根節點的函式 ```cpp=3 auto find = [&anc](int a) { if(anc[a] == a) return a; else return find(anc[a]); } ``` 但是這會產生一個問題,每次詢問最差會跑到 $O(n)$ 所以必須做一些改進 ## find (fixed) 可以發現對於誰是誰的孩子、誰是誰的父親其實不重要 **重要的只有根節點是誰**,所以能盡量往上指就往上指 在往上遞迴的過程中,可以將經過的所有點的父節點全部都指向根節點 這樣下次詢問就可以降成 $O(1)$ 這種手法叫做**路徑壓縮** ```cpp=3 auto find = [&anc](int a) { if(anc[a] == a) return a; else return anc[a] = find(anc[a]); } ``` 或者我們常常壓成一行 ```cpp=3 auto find = [&anc](int a) { return anc[a] == a ? a : anc[a] = find(anc[a]); } ``` ## join 用來把兩個點所屬的集合合併 ```cpp= auto join = [&anc, &find](int a, int b) { if(find(a) == find(b)) return false; anc[find(a)] = find(b); return true; } ``` 這樣就完成最簡單的合併了,而通常這樣就夠快了 不過可能有些人注意到了,`sz`根本沒用到阿 度,因為接下來的優化才會用到它 ## join (fixed) 我們在合併時,會希望每個點到根節點的距離越短越好 所以比較小的集合的根節點應該指向大集合的根節點,才能有效降低期望距離 這種手法稱為**啟發式合併** i.e. ```cpp= auto join = [&anc, &sz, &find](int a, int b) { if(find(a) == find(b)) return false; if(sz[find(a)] < sz[find(b)]) sz[find(b)] += sz[find(a)], anc[find(a)] = find(b); else sz[find(a)] += sz[find(b)], anc[find(b)] = find(a); return true; } ```
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up