SDU程序设计思维Week8-作业 C-班长竞选

程序设计思维Week8-作业

C-班长竞选

Description

大学班级选班长,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开始,紧跟着是最高的票数。 接下来一行输出得票最多的同学的编号,用空格隔开,不忽略行末空格

Sample

Input:
2
4 3
3 2
2 0
2 1

3 3
1 0
2 1
0 2

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

Idea

前提:用前向星存储图

  • 用Kosaraju求SCC
    ① 第一遍DFS遍历原图,确定原图的后序序列
    ② 将后序序列变成逆后序序列,第二遍DFS按照逆后序序列遍历反图(各边倒转),每次由起点遍历到的点构成一个SCC,遍历过程中记录每个SCC中点的数目。
  • 缩点
    将同一个SCC的点合并成一个点,遍历反图中的每个点的边,如果边的两端不在同一SCC中则加入该边,最终形成新的缩点后的图。
  • 计算每个SCC获得的票数
    前提:同一SCC中的点获得票数相同
    遍历缩点后的图,对于入度为0的点(缩点后的图源于反图,所以在原图就是出度为0的点,所有可以到达它的点,都给它投票),进行第三遍DFS计算该点总共获得的票数(去除自己)
  • 计算所有SCC中的最高票数
  • 对拥有最高票数的SCC标记
  • 遍历原图的所有点,如果某点在被标记的SCC中,则输出该点(获得最高票数)

Summary

这道题很综合,属实复杂,基本方法是前向星存图+Kosaraju求SCC+DFS
整个过程中,共需要三张图——原图、反图、缩点后的图,三遍DFS,用到的变量、数组很多所以要注意初始化以及输出格式。
要注意N个同学的序号是0~N-1

Codes

#include <iostream>
#include <queue>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 5010;
const int maxm = 30020;
const int inf = 1e9;
int T, n, m, tot = 0, tot1 = 0, tot2 = 0, dcnt = 0, scnt = 0,temp=0;
int head[maxn],head1[maxn],head2[maxn], vis[maxn], dfn[maxn],c[maxn],scc[maxn],in_deg[maxn],flag[maxn],ans[maxn],maxSCC[maxn];
struct edge {
	int u, v, next;
}e1[maxm],e2[maxm],e[maxm];
//原图
void add1(int u, int v) {
	e1[++tot1].u = u;
	e1[tot1].v = v;
	e1[tot1].next = head1[u];
	head1[u] = tot1;
}
//反图
void add2(int u, int v) {
	e2[++tot2].u = u;
	e2[tot2].v = v;
	e2[tot2].next = head2[u];
	head2[u] = tot2;

}
//缩点
void add(int u, int v) {
	e[++tot].u = u;
	e[tot].v = v;
	e[tot].next = head[u];
	head[u] = tot;
	in_deg[v]++;

}

void dfs1(int x) {
	vis[x] = 1;
	for (int i = head1[x]; i != 0; i = e1[i].next)
	{
		
		int y = e1[i].v;

		if (!vis[y]) { 
			dfs1(y);
		}

	}
	dfn[++dcnt] = x;	//后序 

}

//在反图中按照逆后序遍历构成 SCC
void dfs2(int x) {
	c[x] = scnt;
	scc[scnt]++;
	for (int i = head2[x]; i != 0; i = e2[i].next)
	{
		
		int y = e2[i].v;
		if (!c[y])dfs2(y);
	}

}

void kosaraju() {
	dcnt = 0, scnt = 0;
	memset(c, 0, sizeof(c));
	memset(vis, 0, sizeof(vis));

	for (int i = 1; i <= n; i++)
		if (!vis[i])dfs1(i);
	//cout << endl;
	//逆后序
	for (int i = n; i >= 1; i--)
	{
		//cout << dfn[i] << endl;
		if (!c[dfn[i]])++scnt, dfs2(dfn[i]);
	}
}
//缩点图
void together() {
	for (int x = 1; x <= n; x++) {
		for (int i = head2[x]; i != 0; i = e2[i].next) {
			int y = e2[i].v;
			if (c[x] == c[y])continue;
			add(c[x], c[y]);

		}

	}
}

void dfs(int x) {
	temp += scc[x];
	flag[x] = 1;
	for (int i = head[x]; i != 0; i = e[i].next)
	{
		int y = e[i].v;
		if(!flag[y])dfs(y);
	}
}

void init() {
	for (int i = 0; i <= n; i++) {
		head[i] = 0;
		head1[i] = 0;
		head2[i] = 0;
		tot = 0;
		tot1 = 0;
		tot2 = 0;
		dcnt = 0, scnt = 0, temp = 0;
		vis[i] = 0;
		dfn[i] = 0;
		scc[i] = 0;
		c[i] = 0;
		in_deg[i] = 0;
		flag[i] = 0;
		ans[i] = 0;
		maxSCC[i] = 0;
	}
}

int main()
{
	cin.sync_with_stdio(false);
	cin >> T;
	int num = 1;
	while (T--) {
		cin >> n >> m;
		init();

		for (int i = 1; i <= m; i++) {
			int a, b;
			cin >> a >> b;
			a++, b++;
			add1(a, b);
			add2(b, a);
		}
		kosaraju();
		together();

		//ans = 0;
		for (int i = 1; i <= scnt; i++) {
			if (in_deg[i]==0) { 
				temp = 0;
				memset(flag, 0, sizeof(flag));
				//ans[i] += scc[i] - 1;
				dfs(i);
				ans[i] += (temp - 1);
			}
		}
		int maxans = 0;

		for (int i = 1; i <= scnt; i++)
			if (maxans < ans[i]) { maxans = ans[i]; }

		for (int i = 1; i <= scnt; i++) {
			if (ans[i] == maxans)maxSCC[i]=1;
		}

		printf("Case %d: %d\n", num, maxans);
		int pos = 0;
		for (int i = 1; i <= n; i++) {
			if (maxSCC[c[i]]) {
				if (pos == 0) { cout << i - 1; pos++; }
				else cout << " " << i - 1;
			}
			
		}
		cout << endl;
		num++;
	}
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值