week8 - C - 班长竞选(求强连通分量、kosaraju算法应用)

题意:

大学班级选班长,N 个同学均可以发表意见 若意见为 A B 则表示 A 认为 B 合适,意见具有传递性,即 A 认为 B 合适,B 认为 C 合适,则 A 也认为 C 合适 勤劳的 TT 收集了M条意见,想要知道最高票数,并给出一份候选人名单,即所有得票最多的同学,你能帮帮他吗?、
输入格式:
本题有多组数据。第一行 T 表示数据组数。每组数据开始有两个整数 N 和 M (2 <= n <= 5000, 0 <m <= 30000),接下来有 M 行包含两个整数 A 和 B(A != B) 表示 A 认为 B 合适。
输出格式:
对于每组数据,第一行输出 “Case x: ”,x 表示数据的编号,从1开始,紧跟着是最高的票数。 接下来一行输出得票最多的同学的编号,用空格隔开,不忽略行末空格!
输入样例:

2
4 3
3 2
2 0
2 1

3 3
1 0
2 1
0 2

输出样例:

Case 1: 2
0 1
Case 2: 2
0 1 2

思路:

要找到票数最多的人,即找到能到达某点的点数最多。要求出能到达某点的点数最多的点,可以把图反向,求出从每个点开始可以到达的点的个数最多的点,但是如此的复杂度到达了O( n 2 n^2 n2)。
可以通过把图中的各点分为强连通分量 SCC 进行剪枝,同一SCC中的点,票数是相同的。

  • 首先,通过dfs求出后序序列
  • 然后,对反图按照原图的逆后序序列作dfs,每次dfs遍历到的点即构成一个SCC,并进行染色(对数组c[]赋值)。
  • 之后进行缩图,在原图中,对每条边进行判断,若该条边的起点和终点不在同一个SCC中,则将该边插入新的SCC图中。
  • 对SCC的反图中入度为0的顶点进行dfs,求出每个顶点的答案,其中,答案分为两部分:该顶点包含原图顶点个数-1,以及该顶点能到达的顶点中包含原图顶点个数之和。
  • 求出上一步中答案的最大值,并进行输出。
    注意事项: 输出中的 “Case x: ”中最后有一个冒号。

代码:


#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

const int N = 30005;
int n, c[N], dfn[N], vis[N], dcnt, scnt;
vector<int> g1[N], g2[N];
vector<int> scc1[N], scc2[N];//scc缩点图
int wei[N];//记录每个scc里的点数
int sum;//dfs3时计算点的个数

void init() {
	memset(c, 0, sizeof(c));
	memset(dfn, 0, sizeof(dfn));
	memset(vis, 0, sizeof(vis));
	dcnt = 0;
	scnt = 0;
	for (int i = 0; i < n; i++) {
		g1[i].clear();
		g2[i].clear();
		scc1[i].clear();
		scc2[i].clear();
	}
	memset(wei, 0, sizeof(wei));
	sum = 0;
}

void dfs1(int x) {
	vis[x] = 1;
	int sz = (int)g1[x].size();
	for (int i = 0; i < sz; i++) {
		if (!vis[g1[x][i]]) dfs1(g1[x][i]);
	}
	dfn[++dcnt] = x;
}

void dfs2(int x) {
	c[x] = scnt;
	int sz = (int)g2[x].size();
	for (int i = 0; i < sz; i++) {
		if (!c[g2[x][i]]) dfs2(g2[x][i]);
	}
}

void kosaraju() {
	dcnt = scnt = 0;
	memset(c, 0, sizeof(c));
	memset(vis, 0, sizeof(vis));
	for (int i = 0; i < n; i++) {
		if (!vis[i]) dfs1(i);
	}
	for (int i = n - 1; i >= 0; i--) {
		if (!c[dfn[i]]) {
			scnt++;
			dfs2(dfn[i]);
		}
	}
}

void dfs3(int x) {
	vis[x] = 1;
	sum = sum + wei[x];
	for (int i = 0; i < scc2[x].size(); i++) {
		if(!vis[scc2[x][i]])
			dfs3(scc2[x][i]);
	}
}

int main()
{
	int T;
	cin >> T;
	for (int t = 1; t <= T; t++) {
		init();
		int a, b;
		int m;
		cin >> n >> m;
		//输入,造图
		for (int i = 0; i < m; i++) {
			scanf_s("%d%d", &a, &b);
			g1[a].push_back(b);
			g2[b].push_back(a);
		}
		kosaraju();
		//scc缩图
		memset(wei, 0, sizeof(wei));
		for (int i = 0; i < n; i++) {
			wei[c[i]]++;
			int sz = (int)g1[i].size();
			for (int j = 0; j < sz; j++) {
				if (c[i] != c[g1[i][j]]) {
					scc1[c[i]].push_back(c[g1[i][j]]);
					scc2[c[g1[i][j]]].push_back(c[i]);
				}
			}
		}
		//对scc2的每个入度为0的点,即在scc1中出度为0的点
		//做dfs,求出ans
		int ans = 0;
		vector<int> scc;
		for (int i = 1; i <= scnt; i++) {
			if (scc1[i].size() == 0) {
				sum = 0;
				memset(vis, 0, sizeof(vis));
				dfs3(i); //cout << sum << endl;
				if (sum > ans) {
					ans = sum;
					scc.clear();
					scc.push_back(i);
				}
				else if (sum == ans) {
					scc.push_back(i);
				}
			}
		}
		cout << "Case " << t << ": ";
		cout << ans - 1 << endl; //cout << scc.size() << endl;
		bool first = true;
		for (int i = 0; i < n; i++) {
			for (int j = 0; j < scc.size(); j++) {
				if (c[i] == scc[j]) {
					if (first) {
						cout << i;
						first = false;
					}
					else {
						cout << " " << i;
					}
				}
			}
		}
		cout << endl;
	}
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值