题意
大学班级选班长,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
解题思路
我们可以知道,1投票给2,2投票给3,那么3得到了2票。
所以一个连通分量里面的所有点的票数都是相同的。
所以我们可以把连通分量压缩成点,然后可以知道 : 最大票数的点肯定在出度为0的点上,如果我们把压点后的图给求个反图,那么就是入度为0的点里存在最大的票。
强连通分量(scc)
利用Kosaraju算法求scc
压点后求反图,然后计算最大票数
Kosaraju算法: DFS1次求逆后序序列,根据逆后序序列DFS遍历染色SCC。
代码实现
#include<iostream>
#include<vector>
#include<algorithm>
#define nmax 5005
#include<cstdio>
using namespace std;
vector<int> G1[nmax], G2[nmax], G3[nmax]; //G1--原图 G2--反图 G3--scc的反图
int N, M, A, B;
bool vis[nmax];
int scc_size; //SCC的个数
int c[nmax]; //该点属于哪个scc
int reder; //染色scc
int dfn[nmax]; //记录序列
int dfni; //索引
int sum[nmax]; //入度为0 的scc的票数
int degree[nmax]; //入度
bool G3vis[nmax][nmax]; //G3存反图时避免重复存入 / 标记
struct scc { vector<int> ppp; }; //缩点
scc Scc[nmax];
void add(int u, int v, int g) //g=1时加G1,g==2时加G2 ,g==3时加G3
{
if (u == v) return;
if (g == 1)
G1[u].push_back(v);
if (g == 2)
G2[u].push_back(v);
if (g == 3)
G3[u].push_back(v);
}
void dfs1(int x) //求后序列
{
vis[x] = 1;
for (int i = 0; i < G1[x].size(); i++)
if (vis[G1[x][i]] == 0) dfs1(G1[x][i]);
dfn[dfni++] = x;
}
void dfs2(int x) //遍历逆后序列
{
c[x] = reder;
for (int i = 0; i < G2[x].size(); i++)
if (c[G2[x][i]] == 0) dfs2(G2[x][i]);
}
void buildG() //构造图
{
for (int i = 1; i <= M; i++)
{
scanf("%d %d",&A,&B);
add(A, B, 1);
add(B, A, 2);
}
}
void ini() //初始化
{
for (int i = 0; i < nmax; i++)
{
G1[i].clear();
G2[i].clear();
dfn[i] = 0;
c[i] = 0;
vis[i] = 0;
for (int j = 0; j <nmax; j++)
G3vis[i][j] = 0;
}
for (int i = 0; i <= scc_size; i++)
{G3[i].clear();
sum[i] = 0;
Scc[i].ppp.clear();
degree[i]=0;
}
dfni = 0;
reder = 0;
scc_size=0;
}
void kosaraju()
{
//求后序列
for (int i = 0; i < N; i++)
{
if (vis[i] == 0) dfs1(i);
}
//dfs 逆后序 ,得到scc
for (int i = N-1; i >= 0; i--)
if (c[dfn[i]] == 0) ++reder, dfs2(dfn[i]); //reder染色SCC
}
void suodian_and_G3() //缩点和创建G3
{
for (int i = 0; i < N; i++)
{
Scc[c[i]].ppp.push_back(i);
for (int j = 0; j < G1[i].size(); j++)
{
if (c[i] != c[G1[i][j]]) //注意这里是scc的反图
{
if (G3vis[G1[i][j]][i] == 0) //判断有没有加入过
{
add(c[G1[i][j]],c[i], 3);
degree[c[i]]++; //scc的反图入度更新
G3vis[G1[i][j]][i]++;
}
}
}
}
}
//SCC求反图是为了这里好求票数
int dfs3(int x) //求第i个SCC的票数
{
vis[x] = 1;
int ans = Scc[x].ppp.size();
for (int i = 0; i < G3[x].size(); i++)
{
if (vis[G3[x][i]] == 0) ans+=dfs3(G3[x][i]);
}
return ans;
}
int main()
{
ios::sync_with_stdio(0);
int count = 0;
int T;
scanf("%d",&T);
for( int Ti=0;Ti<T;Ti++)
{
scanf("%d %d",&N,&M);
count++;
buildG();
kosaraju();
suodian_and_G3();
scc_size = reder;
for (int i = 1; i <= scc_size; i++) //求每个SCC的票数
{
if (degree[i] == 0) {
for (int k = 0; k <= scc_size; k++)
vis[k] = 0;
sum[i] = dfs3(i)-1;
}
}
int smax = 0;
for (int i = 1; i <= scc_size; i++) //找出最大的
{
smax = max(sum[i], smax);
}
int res[nmax]; //答案
int resi = 0;
for (int i = 1; i <= scc_size; i++)
{
if (sum[i] == smax) //找到最大的SCC
{
for (int j = 0; j < Scc[i].ppp.size(); j++)
{
res[resi++] = Scc[i].ppp[j];
}
}
}
sort(res, res + resi);
printf("Case %d: %d\n",count,smax);
printf("%d",res[0]);
for (int i = 1; i < resi; i++)
{
printf(" %d",res[i]);
}
printf("\n");
ini();
}
return 0;
}
小结
Kosaraju算法是一个很精妙的算法,2遍dfs可以求出不同颜色的SCC,然后把SCC压点,取压点后的图的反图,便于dfs3求得sum,然后把最大的压点SCC,提取出来他的点,排序后输出。