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

Week8:班长竞选——强连通分量

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

题目大意
大学班级选班长,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

题目分析
首先解释一下强连通分量的定义:

在有向图G中,如果两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通(strongly connected)。
如果有向图G的每两个顶点都强连通,称G是一个强连通图。
有向图的极大强连通子图,称为强连通分量(strongly connected components)。

对于这道题目,我们可以注意到这一句话:

意见具有传递性,即 A 认为 B 合适,B 认为 C 合适,则 A 也认为 C 合适。

所以,这道题所对应的图,不是强连通图,但是其中是一定有强连通分量的。

那么怎么获得这个图中的所有强连通分量呢?介绍一下Kosaraju算法:
Week8:班长竞选——强连通分量
对于上图,我们可以根据强连通分量的定义知道一下这三个强连通分量:

  • {1,2,3}
  • {4,5,6,7}
  • {8}

Kosaraju算法的步骤:

  • 使用dfs,确定图中的逆后续序列,对于此图即为4 7 6 5 1 2 3 8(逆后续序列并不唯一,此序列是把点1作为dfs起始点得到的)
  • 再dfs一次:按照上面得到的逆后续序列,在原图的反图中进行遍历,每次遍历得到的点即为一个强连通分量。
  • 反图就是原图的所有边反向。
  • 注意:dfs时记得标记visit数组。

解题思路
把这道题图中所有的强连通分量(SCC)缩成点获得一个新的,由SCC形成的图,那么由于题目中描述的“票可以传递”,那么对于某一个SCC中的点得到的票数就有:

  • 当前SCC中的点的数量减一(自己当然不能给自己投票)
  • 在SCC的缩点图中,所有具有通向此SCC的有向边的SCC中的点的数量之和。

即对于某一个SCC[i],其中的点的票数为:SCC[i] - 1 + sum( SCC[j] ),其中j可到达i。
除此之外,稍加思考,可以发现最后答案一定出现在出度为 0 的 SCC
中,因此我们将边反向,对每个入度为 0 的点进行 dfs ,计算其能到达的点的 sum( SCC[j] ),即可得到答案。

所以这道题最大的困难其实是一遍遍dfs和一幅幅图~

给出代码:

#include <iostream>
#include <vector>
#include <cstring>
#include <queue>
#include <algorithm>
const int N = 5005;
using namespace std;

int n, m;
vector<int>G1[N], G2[N], Gx[N];//正向,反向,缩点图
int dcnt, scnt;
int dfn[N], c[N];
int scc[N];//某一scnt里点的个数
int sccvis[N];//某一scnt是否遍历到
int s_deg[N];//度

int vis[N];

int maxgrade;
vector<int>winner;

void Init()
{
	for (int i = 0; i < N; i++)
	{
		G1[i].clear();
		G2[i].clear();
		Gx[i].clear();
	}

	winner.clear();
	memset(vis, 0, sizeof vis);
	memset(dfn, 0, sizeof dfn);
	memset(c, 0, sizeof c);
	memset(scc, 0, sizeof scc);
	memset(s_deg, 0, sizeof s_deg);
	dcnt = 0;
	scnt = 0;
	maxgrade = 0;
}

void dfs1(int x)
{
	vis[x] = 1;
	for (auto y : G1[x])
	{
		if (!vis[y]) dfs1(y);
	}
	dfn[++dcnt] = x;
}

void dfs2(int x)
{
	c[x] = scnt;
	scc[scnt]++;
	for (auto y : G2[x])
	{
		if (!c[y]) dfs2(y);
	}
}

void dfsx(int x, int& grade)
{
	sccvis[x] = 1;
	for (auto y : Gx[x])
	{
		if (!sccvis[y])
		{
			grade += scc[y];
			dfsx(y, grade);
		}
	}
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);

	int t;
	cin >> t;
	int casenum = 0;
	while (t--)
	{
		Init();

		cin >> n >> m;
		for (int i = 0, a, b; i < m; i++)
		{
			cin >> a >> b;
			G1[a + 1].push_back(b + 1);
			G2[b + 1].push_back(a + 1);
		}

		for (int i = 1; i <= n; i++)
		{
			if (!vis[i]) dfs1(i);
		}

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

		for (int i = 1; i <= n; i++)
		{
			for (auto y : G1[i])
			{
				if (c[i] == c[y]) continue;
				
				Gx[c[y]].push_back(c[i]);//保存反图
				s_deg[c[i]]++;
			}
		}

		for (int i = 1; i <= scnt; i++)
		{
			if (!s_deg[i])//入度为0
			{
				int the_ans = scc[i] - 1;
				memset(sccvis, 0, sizeof sccvis);
				dfsx(i, the_ans);

				if (the_ans > maxgrade)
				{
					winner.clear();
				}
				if (the_ans >= maxgrade)
				{
					for (int j = 1; j <= n; j++)
					{
						if (c[j] == i)
						{
							winner.push_back(j - 1);
						}
					}
					maxgrade = the_ans;
				}
			}
		}

		sort(winner.begin(), winner.end());

		cout << "Case " << ++casenum << ": " << maxgrade << endl;
		for (int i = 0; i < winner.size(); i++)
		{
			if (i == winner.size() - 1)
			{
				cout << winner[i] << endl;
			}
			else
			{
				cout << winner[i] << ' ';
			}
		}
	}
}