问题描述
大学班级选班长,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。所以这个题要求我们先缩点,将每个强连通分量缩成一个点后,就不再有环。
设某个强连通分量内元素的个数是 ,缩点后,我们不难发现,对于属于第 个 的点来说, ,其中 能到达 。
所以最终答案一定出现在出度为0的 中,我们可以将每条边反向,然后对入度为0的点进行dfs,计算他能到达所有的点k,最终结果就是 。然后比较所有出度为0的点即可。
要注意的是,最终结果还要顺序输出所有的得票最高的同学,我们可以使用多个priority_queue来存每个scc中成员的负数,然后最终比较的时候统计进入另外一个priority_queue中即可。
完整代码
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <string>
#include <climits>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;
const int maxn=5000+10;
const int maxm=30000+10;
struct node
{
int next,to;
};
node edge[maxm],edge2[maxm],edge3[maxn];//原图,反图,缩点反图
int t,n,m,cnt,cnt2,cnt3,fcnt,ccnt,sum,ans;
int head[maxn],head2[maxn],head3[maxn],f[maxn],ff[maxn],color[maxn],scc[maxn],in_deg[maxn];
bool vis[maxn];
priority_queue<int> q[maxn],q2;
inline void add(node* _edge,int* _head,int x,int y,int& _cnt)
{
_cnt++;
_edge[_cnt].to=y;
_edge[_cnt].next=_head[x];
_head[x]=_cnt;
}
void dfs1(int x)//确定逆后序序列
{
vis[x]=true;
for (int i=head[x]; i!=-1; i=edge[i].next){
if(!vis[edge[i].to])
dfs1(edge[i].to);
}
f[x]=++fcnt;
}
void dfs2(int x)//反图按照逆后序遍历染色,顺便统计每种颜色的点的个数
{
color[x]=ccnt; scc[ccnt]++; q[ccnt].push(-x);
for (int i=head2[x]; i!=-1; i=edge2[i].next){
if(!color[edge2[i].to])
dfs2(edge2[i].to);
}
}
void dfs3(int x)
{
vis[x]=true;
for (int i=head3[x]; i!=-1; i=edge3[i].next){
if(!vis[edge3[i].to])
{
sum+=scc[edge3[i].to];
dfs3(edge3[i].to);
}
}
}
inline void kosaraju()//找scc
{
ccnt=fcnt=0;
for (int i=1; i<=n; i++) if(!vis[i]) dfs1(i);
for (int i=1; i<=n; i++) ff[f[i]]=i;
for (int i=n; i>=1; i--){
if(!color[ff[i]]){
++ccnt;
dfs2(ff[i]);
}
}
}
inline void indent()//缩点反向重构,缩图后,图中所有的结点都是颜色
{
for (int i=1; i<=n; i++){
for (int j=head2[i]; j!=-1; j=edge2[j].next){
if(color[i]!=color[edge2[j].to]){
add(edge3,head3,color[i],color[edge2[j].to],cnt3);
in_deg[color[edge2[j].to]]++;//j的入度+1
}
}
}
}
inline void init()//初始化
{
cnt=cnt2=cnt3=ans=sum=fcnt=ccnt=0;
memset(in_deg,0,sizeof(in_deg));
memset(head,-1,sizeof(head));
memset(head2,-1,sizeof(head2));
memset(head3,-1,sizeof(head3));
memset(vis,false,sizeof(vis));
memset(color,0,sizeof(color));
memset(scc,0,sizeof(scc));
memset(f,0,sizeof(f));
while(!q2.empty()) q2.pop();
for (int i=1; i<=n; i++) {
while(!q[i].empty())
q[i].pop();
}
}
inline void solve()//在缩完的图中进行最终求解
{
for (int i=1; i<=ccnt; i++){
if(in_deg[i]==0){
sum=scc[i]-1;
memset(vis,false,sizeof(vis));
dfs3(i);
if(ans<sum){
while(!q2.empty()) q2.pop();
while(!q[i].empty()){
q2.push(q[i].top());
q[i].pop();
}
ans=sum;
}
else if(ans==sum){
while(!q[i].empty()){
q2.push(q[i].top());
q[i].pop();
}
}
}
}
}
inline void print(int _tt)
{
printf("Case %d: %d\n%d",_tt,ans,-q2.top()-1);q2.pop();
while(!q2.empty()){ printf(" %d",-q2.top()-1); q2.pop();}
printf("\n");
}
int getint()
{
int x=0,s=1;
char ch=' ';
while(ch<'0' || ch>'9'){
ch=getchar();
if(ch=='-') s=-1;
}
while(ch>='0' && ch<='9'){
x=x*10+ch-'0';
ch=getchar();
}
return x*s;
}
int main()
{
t=getint();
for (int tt=1; tt<=t; tt++){
init();
n=getint(); m=getint();
for (int i=1; i<=m; i++){
int a=getint(); int b=getint();
add(edge,head,a+1,b+1,cnt);
add(edge2,head2,b+1,a+1,cnt2);//0到n-1变成1到n
}
kosaraju();
indent();
solve();
print(tt);
}
return 0;
}