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