1. 程式人生 > 實用技巧 >2020牛客多校第三場 G - Operating on a Graph (並查集+list)

2020牛客多校第三場 G - Operating on a Graph (並查集+list)

Operating on a Graph

題意:

給定一個無向圖,有n個點,點i初始時屬於集合i。給出q個操作,每次操作針對集合oi,將與集合oi相鄰的集合全部加入集合oi中(若集合oi已經不存在了就無事發生)。在q個操作結束之後,問每個點屬於的集合。

解法:

顯然應該用並查集維護每個點屬於哪個集合。問題在於進行操作時,如何確定和集合oi相鄰的集合?因此每個集合必須要有一個容器儲存其中的點,這個容器還要能夠快速合併。因此選用List作為集合的容器。

在一次操作中,有一些點被展開,和它相鄰的點屬於的集合都被歸入oi,可以發現,這個點只需要被擴充套件一次就夠了,因為之後它相鄰的點一定是和它屬於同一個集合的。

所以在一次操作中,只需要將list中原有的所有點都擴充套件並彈出,並加入相鄰集合的點,同時維護並查集關係。

程式碼:

#include <bits/stdc++.h>
using namespace std;
const int maxn=8e5+5;
vector<int>E[maxn];
list<int>ls[maxn];
int fa[maxn];
int find(int x){
    return x==fa[x]?x:fa[x]=find(fa[x]);
}
int unite(int u,int v){
    int fu=find(u);
    int fv=find(v);
    if(fu!=fv){
        fa[fu]=fv;
        ls[fv].splice(ls[fv].end(),ls[fu]);
    }
}
void bfs(int oi){
    if(find(oi)!=oi){//不存在該集合
        return;
    }
    int size=ls[oi].size();//只要展開集合中原有的點,防止展開新的點
    for(int i=0;i<size;i++){
        int curnode=ls[oi].front();
        for(auto v:E[curnode]){
            unite(v,oi);//將v加入oi
        }
        ls[oi].pop_front();
    }
}
void init(int n){
    for(int i=0;i<n;i++){
        fa[i]=i;
        E[i].clear();
        ls[i].clear();
        ls[i].push_back(i);//初始化集合元素為自己
    }
}
int main () {
    int T;
    scanf("%d",&T);
    while(T--){
        int n,m;
        scanf("%d%d",&n,&m);
        init(n+3);
        for(int i=1;i<=m;i++){
            int u,v;
            scanf("%d%d",&u,&v);
            E[u].push_back(v);
            E[v].push_back(u);
        }
        int q;
        scanf("%d",&q);
        while(q--){
            int oi;
            scanf("%d",&oi);
            bfs(oi);
        }
        for(int i=0;i<n;i++){
            printf("%d",find(i));
            if(i<n){
                printf(" ");
            }
        }
        puts("");
    }
}