题目描述
大学班级选班长,N 个同学均可以发表意见 若意见为 A B 则表示 A 认为 B 合适,意见具有传递性,即 A 认为 B 合适,B 认为 C 合适,则 A 也认为 C 合适 勤劳的 TT 收集了M条意见,想要知道最高票数,并给出一份候选人名单,即所有得票最多的同学,你能帮帮他吗?
Input
本题有多组数据。第一行 T 表示数据组数。每组数据开始有两个整数 N 和 M (2 <= n <= 5000, 0 <m <= 30000),接下来有 M 行包含两个整数 A 和 B(A != B) 表示 A 认为 B 合适。
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
题目分析
本题是给定带正环的无权有向图,求某点最大累积票数(即投票可传递)。由于是无权图,使用vector< int >G[MAXN]存图显得尤为方便(但是会比前向星慢一点)。
思路:求出 SCC 并缩点,即将互相可达与单向可达分开考虑。缩点后,不难发现对于属于第 i 个 SCC 的点来说( 令 SCC[i] 表示第 i 个 SCC 中点的个数),答案分为两部分:
- 当前 SCC 中的点,ans += SCC[i] – 1(去除自己)
- 其它 SCC 中的点 ,SUM ( SCC[j] ),其中 j 可到达 i
然后可以发现最后答案一定出现在原图出度为 0 的 SCC 中,即用反图缩点图跑dfs,记录最大值所在的点即可。
具体操作
首先存好正图和反图,然后加载kosaraju算法计算SCC(强连通分量):从0号点开始,第一遍dfs求DFS后序;然后按照DFS逆后序顺序,第二遍dfs求SCC,此时SCC[MAXN]中存储的是i号点所在的SCC编号。
然后进行缩点操作:缩点操作要进行两次,使用原图缩点图判断出度是否为0,使用反图缩点图进行投票传递计算DFS。
之后遍历反图缩点图中每个入度为0的点进行bfs,同时记录最大值,记得答案要-1,除去自己给自己的投票。
最后循环输出答案即可。
注意
- 可能有多个SCC答案相同!都要记录!
- 本题输入量较大,使用以下三行关同步:
ios::sync_with_stdio(false);//不要忘记ios::
cin.tie(0);
cout.tie(0);
代码
#define _CRT_SECURE_NO_WARNINGS
#define _ ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#include <bits/stdc++.h>
//#include <iostream>
//#include<algorithm>
using namespace std;
int T, N, M;
vector<int>G1[5009];//原图
vector<int>G2[5009];//反图
vector<int>G3[5009];//原图缩点图
vector<int>G4[5009];//反图缩点图
//int zeroDegSCC[5009];//出度为0的SCC号
int SCCcnt[5009];//SCCcnt[i]表示第i个SCC中点的个数
int temp,maxAns, ans[5009];//临时答案数,最大答案数,答案数组
int dcnt;//DFS计数
bool vis[5009];//DFS后序记录是否访问过
int dfsBack[5009];//arr[i]:DFS后序第i个对应的点
void dfs1(int x)
{//求DFS后序
vis[x] = 1;
for (auto y : G1[x])
if (!vis[y])dfs1(y);
dfsBack[++dcnt] = x;
}
int scnt;//SCC计数
int SCC[5009];//SCC[i]:i号点所在的SCC编号
void dfs2(int x)
{//求SCC
SCC[x] = scnt;
for (auto y : G2[x])
if (!SCC[y])dfs2(y);
}
void kosaraju()
{
dcnt = scnt = 0;//最后结果都是从1开始
memset(dfsBack, 0, sizeof(dfsBack));
memset(SCC, 0, sizeof(SCC));
memset(vis, 0, sizeof(vis));
for (int i = 0; i < N; i++)
if (!vis[i])dfs1(i);
for (int i = N; i >= 1; i--)
{
int u = dfsBack[i];
if (!SCC[u])++scnt, dfs2(u);
}
}
void compact()
{//缩点
for (int x = 0; x < N; x++)
for (auto y : G1[x])
{
if (SCC[x] == SCC[y])continue;//属于同一个SCC,不用管
int u = SCC[x];
int v = SCC[y];
G3[u].push_back(v);//不属于同一个SCC,使其构造新图
}
for(int x=0;x<N;x++)
for (auto y : G2[x])
{
if (SCC[x] == SCC[y])continue;//属于同一个SCC,不用管
int u = SCC[x];
int v = SCC[y];
G4[u].push_back(v);//不属于同一个SCC,使其构造新图
}
}
bool vis2[5009];
void dfs3(int x)
{//计算某个SCC能到达的SCC之和
temp += SCCcnt[x];
vis2[x] = 1;
for (auto y : G4[x])
if (!vis2[y])dfs3(y);
}
int main()
{
_;
int A, B;
cin >> T;
for (int i = 1; i <= T; i++)
{
memset(G1, 0, sizeof(G1));
memset(G2, 0, sizeof(G2));
memset(G3, 0, sizeof(G3));
memset(G4, 0, sizeof(G4));
memset(SCCcnt, 0, sizeof(SCCcnt));
memset(ans, 0, sizeof(ans));
cin >> N >> M;
for (int i = 1; i <=M; i++)
{
cin >> A >> B;
G1[A].push_back(B);
G2[B].push_back(A);
}
kosaraju();
//for (auto a : dfsBack)cout << a << ' ';
//cout << endl;
//for (auto a : SCC)cout << a << ' ';
//cout << endl;
compact();
for (int i = 0; i < N; i++)
SCCcnt[SCC[i]]++;
//for (auto a : SCCcnt)cout << a << ' ';
//cout << endl;
temp = maxAns = 0;
for (int i = 1; i <= scnt; i++)
{
if (G3[i].empty())
{//某个SCC出度为0,答案一定在原图出度为0,即反图入度为0
//使用原图缩点图判断出度是否为0,使用反图缩点图DFS
memset(vis2, 0, sizeof(vis2));
temp = 0;
//temp += SCCcnt[i] - 1;//去除自己
dfs3(i);
temp -= 1;//去除自己,第一次dfs3已经加上了自己
//if (temp > ans)ans = temp, SCCnum = i; 可能有多个SCC答案相同!都要记录!
ans[i] = temp;
if (temp > maxAns)maxAns = temp;
}
}
//for (int k = 1; k <= scnt; k++)cout << ans[k]<<endl;
//cout <<"ans"<< ans<<"SCCnum"<<SCCnum << endl;
vector<int> stu;//最高票学生集合
for (int k=1;k<=scnt;k++)
{
if (ans[k] == maxAns)
{
for (int i = 0; i < N; i++)
if (SCC[i] == k)stu.push_back(i);
}
}
sort(stu.begin(), stu.end());
cout << "Case " << i << ": " << maxAns << endl;
for (int i = 0; i < stu.size(); i++)
{
if (i == 0)cout << stu[0];
else cout << ' ' << stu[i];
}
cout << endl;
}
}