题意:有n个人,编号1 - n,有m对朋友,现在希望构造一个入教堂的序列,使得入教堂时在它之前没有它的朋友入教堂的人最少(入教堂时没有朋友比他先入,他就会unhappy,要使unhappy的人最少)。
解法:因为要unhappy的人最少,不难想到各个连通分量可以做到只有一个人unhappy,而每个连通分量至少有一个人unhappy,保证unhappy的人数最少的方法就是遍历各个连通分量,沿着遍历序将人塞进教堂。
要保证字典序最小的方法是使用优先级队列进行图的遍历,优先扩展标号最小的点,将多个块用一个根结点连起来,每个点都连一条边到n + 1,(或者将各个连通分量的最小标号点入队)。
写法可以是并查集 或者 BFS搜连通块。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 10;
vector<int> g[maxn];
int p[maxn],belong[maxn],vis[maxn],ans[maxn],use[maxn];
int t,n,m,cur,res;
int x,y,kas;
void bfs(int s) {
queue<int> pq;
pq.push(s);
vis[s] = kas;
while(!pq.empty()) {
int top = pq.front();
pq.pop();
for(int i = 0; i < g[top].size(); i++) {
int v = g[top][i];
if(vis[v] != kas) {
pq.push(v);
vis[v] = kas;
}
}
}
}
priority_queue<int,vector<int>,greater<int> > pq;
int main() {
scanf("%d",&t);
kas = 0;
while(t--) {
kas++;
cur = 0;
res = 0;
scanf("%d%d",&n,&m);
for(int i = 1; i <= n; i++) {
g[i].clear();
}
for(int i = 1; i <= m; i++) {
scanf("%d%d",&x,&y);
g[x].push_back(y);
g[y].push_back(x);
}
for(int i = 1; i <= n; i++) {
if(vis[i] != kas) {
bfs(i);
use[i] = kas;
pq.push(i);
res++;
}
}
while(!pq.empty()) {
int top = pq.top();
pq.pop();
ans[++cur] = top;
for(int i = 0; i < g[top].size(); i++) {
int v = g[top][i];
if(use[v] != kas) {
pq.push(v);
use[v] = kas;
}
}
}
printf("%d\n",res);
for(int i = 1; i <= n; i++) {
if(i - 1) printf(" ");
printf("%d",ans[i]);
}
cout << endl;
}
return 0;
}