1. 程式人生 > >【資料結構】帶權並查集

【資料結構】帶權並查集

# 目錄 + 簡介 + 詳細介紹 + 例題 ## 簡介 顧名思義,就是在維護集合關係的樹中新增邊權的並查集,這樣做可以維護更多的資訊。 引入題目:https://www.luogu.com.cn/problem/P2024 比如這道題,如果使用普通的並查集則無法處理,因為普通的並查集只能夠刻畫兩個物品是否屬於同一個集合。因此這時候就要使用能夠記錄更多資訊的**帶權並查集**。 > 在閱讀前,需要先掌握**並查集**的知識。 ## 詳細介紹 結合題目講解:https://www.luogu.com.cn/problem/P2024 ![](https://img2020.cnblogs.com/blog/2185228/202102/2185228-20210217222142134-320848444.png) 對於一個物種(一類動物),如果存在它吃另一個物種的關係,則讓它的度數比另一個物種多 $1$ 。更嚴格地說,我們記該物種為 `a` (並非題意中的`A`),另一個物種是 `b`,它們對應的度數為`d[]`,那麼有 $d[a]=d[b]+1$ 。如圖: ![](https://img2020.cnblogs.com/blog/2185228/202102/2185228-20210217223008323-2035607542.png) 那麼有了這樣的規定,便有如下性質: + `d[a]%3==d[b]%3` 時,`a`,`b`是同一個物種。(操作1) + `((d[a]-d[b])%3+3)%3==1` 時,存在`a`吃`b`的關係。(這裡多次取模是為了保證左邊的值只可能為 $0,1,2$ )(操作2) > 從上面的性質可以看出,兩個物種的關係與它們的模數(這題是 $mod3$ )餘多少關係密切相關,因此接下來我們也會著重考察兩個數之間的這種關係。 利用度數以及並查集,即可將各種動物之間的關係刻畫清楚: 這裡依然對`a`,`b`進行討論,為了方便,我們記`a`的祖宗(根節點)為`pa`,`b`的祖宗(根節點)為`pb`。 ![](https://img2020.cnblogs.com/blog/2185228/202102/2185228-20210217224410185-2017022993.png) + 若`pa`,`pb`不在同一個集合中: 就進行並查集的**合併**操作,讓`f[pa]=pb`。可以看出,在合併的時候,仍然作為根節點的`pb`的度數還是 $0$,**但是`pa`的度數需要作出調整**,才能夠保證結點之間關係的正確。 ① 如果`a`和`b`是同一個物種(操作1):則有 `d[pa]+d[a]=d[b]` ② 如果`a`吃`b`(操作2):則有 `d[pa]+d[a]-d[b]=1`(當然,右式等於 $4,7,10$ 這樣的數也是可以的,我們只需找到 $mod 3餘1$的數 ) ![](https://img2020.cnblogs.com/blog/2185228/202102/2185228-20210217225624070-943305788.png) + 若`pa`,`pb`在同一個集合中: 類似於上面的討論, ① 如果`a`和`b`是同一個物種(操作1):如果 `((d[a]-d[b])%3+3)%3!=0`,則矛盾,這句話便是謊言。 ② 如果`a`吃`b`(操作2):如果 `((d[a]-d[b])%3+3)%3!=1`,則矛盾,這句話便是謊言。 綜上,我們的討論將所有情況覆蓋了。 **路徑壓縮:** 根據並查集的性質,如果不進行路徑壓縮,時間複雜度將會退化到 $O(N)$ 。因此帶權並查集也要進行路徑壓縮,那麼主要問題就是解決如何維護`d[]`(度數)的問題: 概括地說,就是在查詢到某個點的時候,在搜尋它的祖宗時遞迴地求出路上所有結點的度數,那麼它的度數就是`d[x]+=d[f[x]]`。 ![](https://img2020.cnblogs.com/blog/2185228/202102/2185228-20210217232151218-1548099975.png) 如上圖,`pa`在一次操作中併入了`pb`。 而在另一次操作中,對`a`的進行了查詢(求祖宗),便有如下路徑壓縮的並更新`d[]`的過程: 遞迴地找出祖宗`pb`。 `pa`的祖宗就是`pb`,度數在合併的時候已經求出來了,所以更新 $0$。 `c`的父親節點是`pa`,合併的時候並沒有更新(因此記錄的是距離`pa`的度數),度數需要加上 $d[pa]$,然後進行路徑壓縮。 ![](https://img2020.cnblogs.com/blog/2185228/202102/2185228-20210217233433590-208134830.png) `a`的父親節點是`c`,在上一步更新了,所以度數加上 $d[c]$ 即可,類似的,進行路徑壓縮。 ![](https://img2020.cnblogs.com/blog/2185228/202102/2185228-20210217233600925-924993944.png) (這裡可能有點難理解,不過只要記住:所謂的`d[x]`指的是節點`x`相對於它父節點的度數即可) > 不理解的地方可以結合程式碼理解 放上程式碼:(~~很短的~~) ``` #include
using namespace std; const int N=5e4+5; int f[N],d[N]; int find(int x){ if(x!=f[x]){ int root=find(f[x]); d[x]+=d[f[x]]; f[x]=root; } return f[x]; } int main(){ int n,m; cin>>n>>m; for(int i=1;i>op>>a>>b; //2,3 judge if(a>n || b>n){ cnt++; continue; } if(a==b && op==2){ cnt++; continue; } int pa=find(a),pb=find(b); int t= op==2; if(pa==pb){ if(((d[a]-d[b])%3+3)%3!=t) cnt++; }else{ f[pa]=pb; d[pa]=t+d[b]-d[a]; } } cout< 程式碼
#include using namespace std; const int N=2e4+5; unordered_map h; int n,m; int f[N]; int d[N]; int get(int x){ if(h.count(x)==0) h[x]=++n; return h[x]; } int find(int x){ if(f[x]!=x){ int root=find(f[x]); d[x]^=d[f[x]]; f[x]=root; } return f[x]; } int main(){ cin>>n>>m; n=0; //init for(int i=1;i>a>>b>>op; a=get(a-1); b=get(b); int t= op=="odd"; int pa=find(a),pb=find(b); if(pa==pb){ if(abs(d[a]-d[b])!=t){ ans=i-1; break; } }else{ //merge f[pa]=pb; d[pa]=d[a]^d[b]^t; } }