## 並查集 Disjoint Set [例題](https://judge.cchs.chc.edu.tw/ShowProblem?problemid=a531) :::info 註 : 並查集有很多種寫法,這邊介紹的是我常用的寫法。 可以看看網路上面是怎麼教的。 https://oi-wiki.org/ds/dsu/ https://hackmd.io/@CLKO/rkRVS_o-4 ::: 又稱互斥集或$DSU$,具有下列功能的資料結構。 > 所有元素 $P$ 編號為 $1,2,3,...,n$。 > 1. 對於$P$中的每個元素$t$都恰有一個集合包含該元素。 > 2. 把$A$,$B$兩個元素所在的集合合併到同個集合中。 > 3. 查詢$A$,$B$兩個元素所在的集合是否是同一個集合。 > 由於 $STL$ 並沒有支援所有我們需要自己實作,值得慶幸的是,這個資料結構實作比較輕鬆。 並查集 範例 : ![image](https://hackmd.io/_uploads/B1FgkehZeg.png) 1. 首先,我們需要紀錄每個元素的 $Boss$ ,每個元素的 $Boss$ 一開始都是自己,如果一個元素的 $Boss$= 該元素,那這個元素我們就定義它是最大的 $Boss$。 2. 如果需要判斷兩個元素是否在同一個集合,可以判斷兩個元素的最大的 $Boss$ 是否相同。 3. 如果要合併兩個集合,就把其中一個元素的最大的 $Boss$ 指定為另外一個元素最大的的 $Boss$ 。 範例 : ```cpp= #include<bits/stdc++.h> using namespace std; const MAXN=2e5+5; vector<int> boss(MAXN, 0); void build_dsu() { for(int i=1;i<MAX;i++) boss[i]=i; } int find(int now) { if(boss[now]==now) return now; return find(boss[now]); } bool same(int x, int y) { return find(x)==find(y); } void union(int x, int y) { int fx=find(x), fy=find(y); boss[fx]=fy; } ``` 這個程式的每次操作的時間複雜度是$O(n)$,因為可以觀察到查詢跟合併的操作都是依靠$find()$這個函式,而一個元素要找到它最大的$boss$最多需要執行$n$次,因此整體的時間複雜度式$O(Qn)$,$Q$是總共有幾次操作。 因此我們可以針對$find()$函式進行優化,我們可以發現一個元素的$Boss$的$Boss$一直到這個元素的最大的$Boss$之前,所有的$Boss$的最大的$Boss$都是相同的,因此我們可以把$find()$改成下面的程式,看起來差不多,但是每次查詢的時間複雜度根據均攤分析可以優化到$O(logn)$,這個優化我們稱之為路徑壓縮。 * [維基百科](https://en.wikipedia.org/wiki/Disjoint-set_data_structure#Time_complexity) * [OI Wiki](https://oi-wiki.org/ds/dsu/) ```cpp= int find(int now) { if(boss[now]==now) return now; return boss[now]=find(boss[now]); } ``` 快還要更快,一個很貪心且直覺的想法就是在合併兩個集合的時候,把比較小的集合的最大的$Boss$,設成比較大的集合的最大的$Boss$,一定比把比較大的集合的最大的$Boss$,設成比較小的集合的最大的$Boss$,因為很顯然的,比較大的集合在更新的最大的$Boss$時需要更多時間,因為元素更多,這樣可以把每次操作的時間優化成$O(\alpha(n))$。 > $\alpha(n)$,的中文是反阿克曼函數,增長很慢,比較直觀的是:$\alpha(2^{2^{2^{2^2}}})=4$,$2^{2^{2^{2^2}}}\approx 2\times10^{19728}$ $DSU .final$ ```cpp= #include<bits/stdc++.h> using namespace std; const MAXN=2e5+5; vector<int> boss(MAXN, 0), sz(MAXN, 1); //一開始的集合大小都是1 void build_dsu() { for(int i=1;i<MAX;i++) boss[i]=i; } int find(int now) { if(boss[now]==now) return now; return boss[now]=find(boss[now]); } bool same(int x, int y) { return find(x)==find(y); } void union(int x, int y) { int fx=find(x), fy=find(y); if(sz[fx]<sz[fy]) swap(fx, fy); sz[fx]+=sz[fy]; boss[fy]=boss[fx]; } ``` 練習題 > [DSU](https://atcoder.jp/contests/abc177/tasks/abc177_d) > [DSU](https://cses.fi/problemset/task/1676/)