【數據結構】帶權並查集

目錄

  • 簡介
  • 詳細介紹
  • 例題

簡介

顧名思義,就是在維護集合關係的樹中添加邊權的並查集,這樣作能夠維護更多的信息。c++

引入題目:https://www.luogu.com.cn/problem/P2024
好比這道題,若是使用普通的並查集則沒法處理,由於普通的並查集只可以刻畫兩個物品是否屬於同一個集合。所以這時候就要使用可以記錄更多信息的帶權並查集spa

在閱讀前,須要先掌握並查集的知識。code

詳細介紹

結合題目講解:https://www.luogu.com.cn/problem/P2024
blog

對於一個物種(一類動物),若是存在它吃另外一個物種的關係,則讓它的度數比另外一個物種多 \(1\) 。更嚴格地說,咱們記該物種爲 a (並不是題意中的A),另外一個物種是 b,它們對應的度數爲d[],那麼有 \(d[a]=d[b]+1\) 。如圖:
遞歸

那麼有了這樣的規定,便有以下性質:ci

  • d[a]%3==d[b]%3 時,ab是同一個物種。(操做1)
  • ((d[a]-d[b])%3+3)%3==1 時,存在ab的關係。(這裏屢次取模是爲了保證左邊的值只可能爲 \(0,1,2\) )(操做2)

從上面的性質能夠看出,兩個物種的關係與它們的模數(這題是 \(mod3\) )餘多少關係密切相關,所以接下來咱們也會着重考察兩個數之間的這種關係。get

利用度數以及並查集,便可將各類動物之間的關係刻畫清楚:string

這裏依然對ab進行討論,爲了方便,咱們記a的祖宗(根節點)爲pab的祖宗(根節點)爲pb
it

  • papb不在同一個集合中:
    就進行並查集的合併操做,讓f[pa]=pb。能夠看出,在合併的時候,仍然做爲根節點的pb的度數仍是 \(0\)可是pa的度數須要做出調整,纔可以保證結點之間關係的正確。
    ① 若是ab是同一個物種(操做1):則有 d[pa]+d[a]=d[b]
    ② 若是ab(操做2):則有 d[pa]+d[a]-d[b]=1(固然,右式等於 \(4,7,10\) 這樣的數也是能夠的,咱們只需找到 \(mod 3餘1\)的數 )
    class

  • papb在同一個集合中:
    相似於上面的討論,
    ① 若是ab是同一個物種(操做1):若是 ((d[a]-d[b])%3+3)%3!=0,則矛盾,這句話即是謊話。
    ② 若是ab(操做2):若是 ((d[a]-d[b])%3+3)%3!=1,則矛盾,這句話即是謊話。

綜上,咱們的討論將全部狀況覆蓋了。

路徑壓縮:
根據並查集的性質,若是不進行路徑壓縮,時間複雜度將會退化到 \(O(N)\) 。所以帶權並查集也要進行路徑壓縮,那麼主要問題就是解決如何維護d[](度數)的問題:
歸納地說,就是在查詢到某個點的時候,在搜索它的祖宗時遞歸地求出路上全部結點的度數,那麼它的度數就是d[x]+=d[f[x]]


如上圖,pa在一次操做中併入了pb
而在另外一次操做中,對a的進行了查詢(求祖宗),便有以下路徑壓縮的並更新d[]的過程:
遞歸地找出祖宗pb
pa的祖宗就是pb,度數在合併的時候已經求出來了,因此更新 \(0\)
c的父親節點是pa,合併的時候並無更新(所以記錄的是距離pa的度數),度數須要加上 \(d[pa]\),而後進行路徑壓縮。

a的父親節點是c,在上一步更新了,因此度數加上 \(d[c]\) 便可,相似的,進行路徑壓縮。

(這裏可能有點難理解,不過只要記住:所謂的d[x]指的是節點x相對於它父節點的度數便可)

不理解的地方能夠結合代碼理解

放上代碼:(很短的

#include<bits/stdc++.h>
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<N;i++) f[i]=i;
    
    int cnt=0;
    while(m--){
        int op,a,b;
        cin>>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<<cnt<<endl;
    
    return 0;
}

例題

https://www.acwing.com/problem/content/241/
分析:思路徹底相似於食物鏈那題。

代碼 #include using namespace std;

const int N=2e4+5;

unordered_map<int,int> 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<N;i++) f[i]=i;

int ans=m;
for(int i=1;i<=m;i++){
    int a,b;
    string op;
    
    cin>>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;
    }
}

cout<<ans<<endl;

return 0;

}

相關文章
相關標籤/搜索