班长竞选-连通子图+缩点

题目描述

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

Input

本题有多组数据。第一行 T 表示数据组数。每组数据开始有两个整数 N 和 M (2 <= n <= 5000, 0

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

解题思路

本题和之前的题目感觉不同的是,看起来可能比较简单,但实际上多加思考以后才会发现解决起来还是非常困难的。
刚看到这个题时首先想到的是使用Floyd算法,因为该算法可以解决传递性的问题,但考虑到可能存在环的问题,并且讲解中并没有提到该方法,就暂时放弃,还是使用标准算法:先使用dfs遍历,从0点开始,找到图的后序序列,然后便找到了逆后序序列,根据这个序列去遍历反图,找到每一个连通子图并作相应的标记。根据连通子图进行缩点,得到缩点后的原图和反图,找到在原图中出度为0的缩点,并在缩点后的反图中以这些点为起点进行dfs遍历,根据缩点后的权值,找到路径最长的缩点,就是最后的所求值。

代码

#include
#include
#include
#include
#include
#include
using namespace std;
const int maxm = 5010, maxn = 3e4 + 10;
int head1[maxm], head2[maxm], out_deg[maxm];//记录每一个点的出度 
int vis[maxm], c[maxm];//c代表某点所在的scc号,vis代表是否遍历过
int scc[maxm], scnt;//scc代表该连通子图中的元素个数,scnt代表连通子图编号 
int dfn[maxm], dcnt;//后序序列和后序序列编号 
int T, N, M, A, B;
int tot1 = 0, tot2 = 0;
int sh_vis[maxm];
int ans[maxm];
pair<int, int> a[maxm];//ans数组 
struct Edge
{
	int u, v, nxt;//出发点,到达点,权值,下一个点 
};
Edge e1[maxn], e2[maxn];//原图和反图的边 
vector<int> sh_gra1[maxm], sh_gra2[maxm];//缩点后的原图和反图 
int temp = 0;
void addedge1(int u, int v)
{
	e1[tot1].u = u;
	e1[tot1].v = v;
	out_deg[u]++;
	e1[tot1].nxt = head1[u];
	head1[u] = tot1;
	tot1++;
}
void addedge2(int u, int v)
{
	e2[tot2].u = u;
	e2[tot2].v = v;
	e2[tot2].nxt = head2[u];
	head2[u] = tot2;
	tot2++;
}
void init()
{
	tot1 = tot2 = 0;
	dcnt = scnt = 0;
	temp = 0;
	for (int i = 0; i <= N; i++)
	{
		a[i].first = a[i].second = 0;
		head1[i] = -1;
		head2[i] = -1;
		out_deg[i] = 0;
		vis[i] = 0;
		scc[i] = 0;
		c[i] = 0;
		ans[i] = 0;
		while (sh_gra1[i].size()) sh_gra1[i].pop_back();
		while (sh_gra2[i].size()) sh_gra2[i].pop_back();
	}
}

void dfs1(int m)
{
	vis[m] = 1;
	for (int i = head1[m]; i != -1; i = e1[i].nxt)
		if (!vis[e1[i].v]) dfs1(e1[i].v);
	dfn[dcnt] = m;
	dcnt++;
}

void dfs2(int m)
{
	c[m] = scnt;
	scc[c[m]]++;//记录该连通子图中的元素个数 
	for (int i = head2[m]; i != -1; i = e2[i].nxt)
		if (!c[e2[i].v]) dfs2(e2[i].v);
}

void get_shorted_graph()
{
	for (int i = 1; i <= N; i++)
		out_deg[i] = 0;
	for (int i = 0; i < N; i++)
		for (int j = head1[i]; j != -1; j = e1[j].nxt)
		{
			int y = e1[j].v;
			if (c[i] == c[y]) continue;
			sh_gra1[c[i]].push_back(c[y]);
			out_deg[c[i]]++;
			sh_gra2[c[y]].push_back(c[i]);//反图 
		}
}

void dfs3(int m)
{
	sh_vis[m] = 1;
	for (auto i : sh_gra2[m])
	{
		if (!sh_vis[i])
		{
			ans[temp] += scc[i];
			dfs3(i);
		}
	}
}
bool cmp(pair<int, int>a, pair<int, int>b)
{
	return a.first > b.first;
}
int main()
{
	std::ios::sync_with_stdio(false);
	cin >> T;
	for (int i = 0; i < T; i++)
	{
		cin >> N >> M;
		init();
		for (int j = 0; j < M; j++)
		{
			cin >> A >> B;
			addedge1(A, B);
			addedge2(B, A);//反图 
		}
		for (int j = 0; j < N; j++)
			if (!vis[j]) dfs1(j);
		for (int j = dcnt - 1; j >= 0; j--)
			if (!c[dfn[j]])
			{
				scnt++;
				dfs2(dfn[j]);
			}
		get_shorted_graph(); //得到缩点后的图
		vector<int> zero_outd;
		for (int j = 1; j <= scnt; j++)
			if (!out_deg[j]) zero_outd.push_back(j);
		int s = zero_outd.size();
		for (int j = 0; j < s; j++)
		{
			memset(sh_vis, 0, sizeof sh_vis);
			ans[temp] += scc[zero_outd[j]] - 1;
			dfs3(zero_outd[j]);

			a[temp].first = ans[temp];
			a[temp].second = zero_outd[j];
			temp++;
		}
		while (zero_outd.size()) zero_outd.pop_back();
		sort(a, a + s, cmp);
		cout << "Case " << i + 1 << ": " << a[0].first << endl;
		priority_queue<pair<int, int> > w;
		pair<int, int> r;
		int h = 0;
		for (int j = 0; j < s; j++)
		{
			h = 0;
			if (a[j].first == a[0].first)
			{
				for (int u = 0; u < N; u++)
				{
					if (c[u] == a[j].second) w.push(make_pair(-u, u)), h++;
					if (h == scc[c[u]]) break;
				}
			}
		}
		while (w.size() > 1)
		{
			cout << w.top().second << " ";
			w.pop();
		}
		cout << w.top().second;
		cout << endl;
	}
	return 0;
}

你可能感兴趣的:(c++)