Disjoint Set

Wu-Jun Pei

Problem

有N個人在不同的平行時空中,接下來有M對人,表示A、B存在在同一個時空,接下來有K對查詢A、B是否存在在同一個時空

N \le 10^6, M \le 10^6, K \le 10^6
N106,M106,K106N \le 10^6, M \le 10^6, K \le 10^6

Problem

​嘗試1

把每條邊視為圖,DFS圖,把同樣的連通元件的節點都用相同的編號,之後每K次查詢就查詢兩個點是否擁有相同的編號。

 

複雜度:O( N + M ) + O( K )

Problem

​嘗試1

時間複雜度:O( N + M ) + O( K )

空間複雜度:O( N + M )

看起來很好寫,其實真的很好寫 OuO

Problem

​嘗試2

並查集(Disjoint Set / Union Find Tree):

是一個支援合併(併)以及查詢(查)的資料結構!

Disjoint Set

每節點有一個指標,指到他的父節點,可以用陣列來實作!

 

struct dsu{
    int par[ maxn ];
};

Disjoint Set

初始化:每一個節點原本都只到自己,意即每個節點都是一個獨立的集合。

 

 

 

 

複雜度:O( N )

struct dsu{
    int par[ maxn ];

    void init( int N ){
        for( int i = 0; i < N; i++ ){
            par[ i ] = i;
        }
    }
};

Disjoint Set

查詢:遞迴地查詢父節點,直到找到根節點。

 

 

 

 

 

複雜度:O( N )

struct dsu{
    int par[ maxn ];
    ...
    int find( int x ){
        return x == par[ x ] ? x : find( par[ x ] );
    }
};

Disjoint Set

合併:將兩個集合合併在一起,只要將B的根節點的父指標指到A即可。

 

 

 

 

複雜度:O( N ) 因為查詢要花很久的時間...

struct dsu{
    int par[ maxn ];
    ...
    void merge( int A, int B ){
        par[ find( A ) ] = B;
    }
};

Disjoint Set

查詢是否在同一個時空:查詢是否有相同的父節點

 

 

 

 

 

複雜度:O( N )

struct dsu{
    int par[ maxn ];
    ...
    int same( int X, int Y ){
        return find( X ) == find( Y );
    }
};

Disjoint Set

時間複雜度:

查詢:O( N )

合併:O( N )

空間複雜度:

O( N )

總複雜度是 O( MN ) + O( KN )

但其實,萬惡的根源就是在於查詢太久了啊QQ

Disjoint Set

小小的優化......路徑壓縮!

查詢:遞迴地查詢父節點,直到找到根節點,並更新。

 

 

 

均攤複雜度:O( logN )

struct dsu{
    int par[ maxn ];
    ...
    int find( int x ){
        return par[ x ] = ( x == par[ x ] ? x : find( par[ x ] ) );
    }
};

Disjoint Set

此時的時間複雜度:

查詢:O( logN )

合併:O( logN )

 

總複雜度是 O( MlogN ) + O( KlogN )

這個時間點我們已經有足夠的能力完成這題了 > <

Disjoint Set, Even Faster!

每節點有一個指標和一個整數,指標指到他的父節點,整數表示集合的大小,也可以用陣列來實作!

struct dsu{
    int par[ maxn ];
    int siz[ maxn ];
};

Disjoint Set, Even Faster!

合併:將兩個集合合併在一起,只要將B的根節點的父指標指到A即可。

 

 

 

 

 

使用啟發適合併,將小的合併到大的!

struct dsu{
    int par[ maxn ];
    ...
    void merge( int A, int B ){
        int X = find( A ), Y = find( B );
        if( siz[ X ] > siz[ Y ] )
            swap( X, Y );
        par[ X ] = Y;
        siz[ Y ] += siz[ X ];
    }
};

Disjoint Set, Even Faster!

此時的時間複雜度是

O( ɑ(N+M, N) )

 ɑ 可視為一個很小的常數

總複雜度是 O( ɑN )

我們更快了xD

Problem

有N個人在不同的平行時空中,接下來有K筆操作,表示:

  • 一對人A、B在同一個時空                                
  • 查詢A、B是否有機會認識

N \le 10^6, K \le 10^6
N106,K106N \le 10^6, K \le 10^6

Problem

​嘗試1

查詢:查詢兩個點是否擁有相同的編號。

合併:使用啟發式合併,將小的圖跟大的圖合併,每次改變小的圖的每個點的編號。

 

總複雜度 O( K( 1 + logN ) )

 

但是...感覺還蠻難實作的

Problem

​嘗試2

 

使用 Disjoint Set 可以不用改 code 再 AC 一題!

Disjoint Set

By Wu-Jun Pei

Disjoint Set

  • 26