题意:
大学班级选班长,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
思路:
具体过程见代码,有详细注释。
代码:
#define _CRT_SECURE_NO_WARNINGS
#include <cstdio>
#include <queue>
#include <cstring>
#include <algorithm>
const int inf=0x3f3f3f;
const int maxn=5000+5;
const int maxm=30000+5;
using namespace std;
int N,M,a,b;
struct edge
{//前向星
int to,next;
} e1[maxm],e2[maxm];//1为原图 2为反图
int head1[maxm],head2[maxm],tot1=0,tot2=0;
void add(int u,int v)
{//加边
//原图
e1[++tot1].to=v;
e1[tot1].next=head1[u];
head1[u]=tot1;
//反图
e2[++tot2].to=u;
e2[tot2].next=head2[v];
head2[v]=tot2;
}
int dfn[maxn],vis[maxn],c[maxn],num[maxn],dcnt=0,scnt=0;
void dfs1(int x)
{//dfs1 生成逆后序序列
vis[x]=1;
for(int i=head1[x]; i; i=e1[i].next)
if(!vis[e1[i].to]) dfs1(e1[i].to);
dfn[++dcnt]=x;
}
void dfs2(int x)
{//强连通分支 c数组为不同的分支做记号 num数组为个分支的元素数
c[x]=scnt;
num[scnt]++;
for(int i=head2[x]; i; i=e2[i].next)
if(!c[e2[i].to]) dfs2(e2[i].to);
}
void kosaraju()
{//算法
dcnt=scnt=0;
memset(c,0,sizeof(c));
memset(vis,0,sizeof(vis));
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]); //强连通分支
}
edge e[maxm]; //缩点后的图
int head[maxm],tot=0,in_deg[maxn],sum[maxn],ans,tmp;
//in_deg缩点后图的入度 sum为缩点后每个点的票数
void addd(int u,int v)
{//加缩点后的边
e[++tot].to=v;
e[tot].next=head[u];
head[u]=tot;
}
int dfs(int s)
{
vis[s]=1;
tmp+=num[s]; //将缩点后每个点的数量加进去
for(int i=head[s]; i; i=e[i].next)
{
if(!vis[e[i].to])
dfs(e[i].to);
}
}
void ini()
{
memset(head1,0,sizeof(head1));
memset(head2,0,sizeof(head2));
memset(head,0,sizeof(head));
memset(in_deg,0,sizeof(in_deg));
memset(num,0,sizeof(num));
memset(sum,0,sizeof(sum));
memset(c,0,sizeof(c));
tot1=tot2=tot=0;
}
int main()
{
int T;
scanf("%d",&T);
for(int t=1; t<=T; t++)
{
ini();
scanf("%d %d",&N,&M);
for(int i=1; i<=M; i++)
{
scanf("%d %d",&a,&b);
add(a+1,b+1); //读入 整体右移
}
kosaraju(); //强连通分支
for(int i=1; i<=N; i++)
{//缩点
for(int j=head1[i]; j; j=e1[j].next)
{
if(c[i]!=c[e1[j].to]) //着色不一样 反向建缩点图
{
addd(c[e1[j].to],c[i]);
in_deg[c[i]]++; //计算入度
}
}
}
ans=-1;
for(int i=1; i<=scnt; i++)
{
if(in_deg[i]==0)
{//每个入度为0 的 就是可行答案 按后选择一个最大的
tmp=0;
memset(vis,0,sizeof(vis));
dfs(i);
sum[i]=tmp; //dfs内tmp为缩点后可以到达的所有点的数目和
ans=max(ans,sum[i]);
}
}
printf("Case %d: %d\n",t,ans-1);
bool cs = 0;
for (int i = 1; i <= N; ++i) {
if (sum[c[i]] == ans) { //等于ans 在同一个连通分支
if (cs != 0) {
printf(" %d", i - 1);
}
else {
cs = 1;
printf("%d", i - 1);
}
}
}
printf("\n");
}
return 0;
}
总结:
缩点后要反向建图,要不然建出来图什么都不是。。。
前向星可以不要权值。