题意:
有n个人要参加聚会,1~n分别表示他们的序列,m行表示他们之间的关系,这n个人一次进场,若进场后发现没有自己的朋友,这个人就会不开心。现在要求排他们的入场顺序,使得不开心的人数最少,同时,进场人的字典序最小。
思路:
由于他们的关系可以用联通图表示,则每一个联通块必然至少会有一个人不开心,所以用并查集判断联通块个数。同时再用0号表示超级源点,依次将他们的朋友放进优先队列中,出队顺序就是入场顺序。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1000050;
int pre[MAXN],cnt; //保存节点的直接父节点
vector<int>ve[MAXN];
int ans[MAXN],vis[MAXN];
//查找x的根节点
int Find(int x){
if(pre[x]!=x)
pre[x]=Find(pre[x]);//路径压缩,本结点更新为根结点的子结点
return pre[x];
}
//连接两个连通块
void join(int x,int y) {
int fx=Find(x),fy=Find(y);
if(fx>fy) pre[fx]=fy;
else pre[fy]=fx;
}
void BFS(int x)
{
priority_queue<int,vector<int>,greater<int> > q;
q.push(x);
while(!q.empty()){
int now = q.top();q.pop();
if(!vis[now]){
vis[now]=1;
ans[cnt++]=now; //出队顺序是入场顺序
for(int i=0;i<ve[now].size();i++){
if(!vis[ve[now][i]]){
q.push(ve[now][i]);
}
}
}
}
}
int main() {
int T;
cin>>T;
while(T--){
int N,M,a,b,i,j,ans1=0;
scanf("%d%d",&N,&M);
//初始化pre数组
for(i=0;i<=N;i++){
pre[i]=i;//根据连通情况,构建pre数组
vis[i]=0;
ve[i].clear();
}
for(i=1;i<=M;i++) {
scanf("%d%d",&a,&b);
ve[a].push_back(b);
ve[b].push_back(a);
join(a,b);
}
for(i=1;i<=N;i++){
if(pre[i]==i){
ans1++; //计算连通子图的个数ans
ve[0].push_back(i);
}
}
cnt=0;
BFS(0);
cout<<ans1<<endl;
for(int i=1;i<cnt;i++){
if(i==1) printf("%d",ans[i]);
else printf(" %d",ans[i]);
}printf("\n");
}
return 0;
}