並查集的二三事

並查集是一個維護集合的數據結構
它能夠方便的進行元素集合的合併,並且查詢每個元素屬於哪個集合
並查集更多的在於關係的傳遞性,集合與集合之間常常因爲一個元素的“搭橋”而合併成爲同一個集合

只要1,2,3,A題很簡單

並查集(simple)

在使用並查集時,我們一般通過維護父子關係來完成對集合的控制
若兩個點的最老祖先相同,則在同一個集合內,否則,不在同一個集合內
若要進行合併集合操作,則直接讓一個點的最老祖先只指向另一個點的最老祖先就好了(保證了兩個集合內所有點的最老祖先都是同一個點)
並查集的操作很簡單,一個find ,一個merge 即可

最簡單的板子:

int find(int x) {
    if(fa[x]==x) return fa[x];
    else return find(fa[x]);
}
void merge(int x,int y) {
    int fx=find(x),fy=find(y);
    fa[fx]=fy;
}

顯然,find 的最大複雜度爲O(n) ,這是不能接受的

隨便YY一下,你會發現對於一個點來說,反正要找的是他的祖先,而不是找fa ,所以我們將fa 在不斷搜索時同時更新爲他的grandfa ,最終更新爲它的祖先即可

這樣複雜度直線下降,可以做到均攤複雜度複雜度O(malpha(n))
alpha 函數一般來說在很大的範圍是不大於4
所以可以將其看爲線性的

grandfa 板子:

int find(int x) {
    if(fa[x]==x) return fa[x];
    else return fa[x]=find(fa[x]);
}

或者簡化版:

int find(int x) {
    return fa[x]==x?fa[x]:fa[x]=find(fa[x]);
}

這個東西一般被稱作路徑壓縮


帶權並查集

誒呀,會並查集了?那你會帶權並查集麼?

學完普通的並查集,讓我們來想想如果並查集中的連邊是有邊權的咋辦?

例題:[NOI2002]銀河英雄傳說

其實很簡單,每個點不僅僅保存一個fa ,我們還保存一個dis ,意味着當前點到fa 的距離.在路徑壓縮是或者在查詢時記得將dis 合併即可
但是注意,原來的簡化寫法就不行了
老老實實的寫遞歸*or非遞歸*版吧

int find(int x) {
    if(x!=fa[x]) {
        int temp=fa[x]; 
        fa[x]=find(fa[x]);
        dis[x]+=dis[temp];
    }
    return fa[x];
}

可持久化並查集

不錯呀,居然連帶權並查集都搞定了?那就試試可持久化並查集吧!

要學會可持久化並查集,首先得學會可持久化線段樹

接下來實現一下可持久化數組

然後用可持久化數組來維護fa

恭喜你,你就能夠A掉可持久化並查集

PS:其實本來還有個拆點並查集的,但是因爲很懶貌似很簡單,所以自已意會一下就好啦!
這也就是可以回退操作的並查集哦!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章