ZOJ:Welcome Party (各个连通分量的遍历 + 总字典序最小)

题意:有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;
}

猜你喜欢

转载自blog.csdn.net/qq_41997978/article/details/89635021