欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

程序设计思维 C - 班长竞选 (强连通分量、kosaraju算法)

程序员文章站 2022-07-12 18:10:41
...

题目

大学班级选班长,N 个同学均可以发表意见 若意见为 A B 则表示 A 认为 B 合适,意见具有传递性,即 A 认为 B 合适,B 认为 C 合适,则 A 也认为 C 合适 勤劳的 TT 收集了M条意见,想要知道最高票数,并给出一份候选人名单,即所有得票最多的同学,你能帮帮他吗?

Input
本题有多组数据。第一行 T 表示数据组数。每组数据开始有两个整数 N 和 M (2 <= n <= 5000, 0 <m <= 30000),接下来有 M 行包含两个整数 A 和 B(A != B) 表示 A 认为 B 合适。

Output
对于每组数据,第一行输出 “Case x: ”,x 表示数据的编号,从1开始,紧跟着是最高的票数。 接下来一行输出得票最多的同学的编号,用空格隔开,不忽略行末空格!

Sample Input
2
4 3
3 2
2 0
2 1
3 3
1 0
2 1
0 2

Sample Output
Case 1: 2
0 1
Case 2: 2
0 1 2

思路

一、强连通分量(SCC)
强连通分量的概念:
程序设计思维 C - 班长竞选 (强连通分量、kosaraju算法)
来源:刘建东学长、黄瑞哲学长的ppt
二、kosaraju算法
kosaraju算法是一种求强连通分量的算法。
1.原图和反图
程序设计思维 C - 班长竞选 (强连通分量、kosaraju算法)
来源:刘建东学长、黄瑞哲学长的ppt
2.kosaraju算法
(1)用DFS在原图找逆后序序列
(2)用DFS在反图中按照逆后序序列遍历,每次由起点遍历到的点即构成一个强连通分量
三、解题
1.构建图
如果A认为B合适,则构建一条从A到B的边(A, B)。
2.求出图的强连通分量
使用kosaraju算法求出图的强连通分量。
3.缩点、求解
SCC[i]:第i个强连通分量中的点的数目
若有一条从第j个强连通分量到第i个强连通分量的边(即第j个强连通分量可达第i个强连通分量),则说明第j个“团体”的同学认为第i个“团体”的同学合适。
故设X在第i个强连通分量中,则认为X合适人数有:
SCC[i] - 1 + sum(SCC[j]),第j个强连通分量可达第i个强连通分量
不难理解,得票最多的同学一定在出度为0的强连通分量中。
故在反图中对每个入度为0的点进行DFS,计算其能到达的点的SUM(SCC[j]),即可得到答案。

代码

#include <iostream>
#include <vector>
#include <cstring>
#include <set>

#define MAX_V 5050

using namespace std;

int T;
int N, M;
int A, B;

vector<int> G1[MAX_V];	// 原图
vector<int> G2[MAX_V];	// 反图
vector<int> SCC[MAX_V];	// 缩点后的图

int dfn[MAX_V]; // 逆后序序列
int vis[MAX_V];
int c[MAX_V];	// c[i]:第i个顶点所在的连通分量(的编号)

int dcnt;	// 逆后序序列计数
int scnt;	// 连通分量计数

int vNum[MAX_V];  // vNum[i]:第i个连通分量中的顶点数

int Max;	// 记录最大的点数
int cnt;    

vector<int> v;
set<int> s[MAX_V];

void dfs1(int x) { // 在原图找逆后序序列
	vis[x] = 1;
	for (int i = 0; i < G1[x].size(); i++)
		if (!vis[G1[x][i]])	dfs1(G1[x][i]);
	dfn[++dcnt] = x;
}

void dfs2(int x) { // 在反图中按照逆后序序列遍历
	c[x] = scnt;
	vNum[scnt]++;
	for (int i = 0; i < G2[x].size(); i++) {
		if (!c[G2[x][i]]) dfs2(G2[x][i]);
	}
}

void kosaraju()
{
	for (int i = 0; i < N; i++)
		if (!vis[i]) dfs1(i);

	for (int i = N - 1; i >= 0; i--)
		if (c[dfn[i]] == 0) {
			scnt++;
			dfs2(dfn[i]);
		}
}

void shrink()
{
	for (int i = 0; i < N; i++)
		for (int j = 0; j < G1[i].size(); j++)
			if (c[i] != c[G1[i][j]]) {
				SCC[c[i]].push_back(c[G1[i][j]]);
				s[c[G1[i][j]]].insert(c[i]);
			}
}

void dfs(int x) {
	vis[x] = 1;
	cnt += vNum[x];
	for (set<int>::iterator it = s[x].begin(); it != s[x].end(); it++)
		if (!vis[*it]) dfs(*it);
}

//void print()
//{
//	for (int i = 1; i <= scnt; i++) {
//		if (!SCC[i].size()) {
//			cnt = 0;
//
//			memset(vis, 0, sizeof(vis));
//
//			dfs(i);
//
//			if (cnt > Max) {
//				Max = cnt;
//				v.clear();
//				v.push_back(i);
//			}
//			else if (cnt == Max)
//				v.push_back(i);
//		}
//	}
//
//	Max--;
//	cout << Max << endl;
//
//	bool end = false;
//	for (int j = 0; j < N; j++)
//		for (int k = 0; k < v.size(); k++)
//			if (c[j] == v[k]) {
//				if (!end) end = true;
//				else cout << " ";
//				cout << j;
//			}
//
//	cout << endl;
//}

void init()
{
	memset(dfn, 0, sizeof(dfn));
	memset(vis, 0, sizeof(vis));
	memset(c, 0, sizeof(c));
	memset(vNum, 0, sizeof(vNum));
	v.clear();
	for (int i = 0; i < N; i++) {
		G1[i].clear();
		G2[i].clear();
		SCC[i].clear();
		s[i].clear();
	}
	scnt = 0;
	dcnt = 0;
	Max = 0;
}

int main() {
	cin >> T;
	for (int t = 1; t <= T; t++) {
		cin >> N >> M;

		init();

		for (int i = 0; i < M; i++) {
			cin >> A >> B;
			G1[A].push_back(B);
			G2[B].push_back(A);
		}

		kosaraju();

		shrink();

		cout << "Case " << t << ":" << " ";

		for (int i = 1; i <= scnt; i++) {
			if (!SCC[i].size()) {
				cnt = 0;

				memset(vis, 0, sizeof(vis));

				dfs(i);

				if (cnt > Max) {
					Max = cnt;
					v.clear();
					v.push_back(i);
				}
				else if (cnt == Max)
					v.push_back(i);
			}
		}

		Max--;
		cout << Max << endl;

		bool end = false;
		for (int j = 0; j < N; j++)
			for (int k = 0; k < v.size(); k++)
				if (c[j] == v[k]) {
					if (!end) end = true;
					else cout << " ";
					cout << j;
				}

		cout << endl;
	}
	return 0;
}