题目描述:
大学班级选班长,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开始,紧跟着是最高的票数。 接下来一行输出得票最多的同学的编号,用空格隔开,不忽略行末空格。
思路:
有向图,在一个SCC中,所有的节点都是互相支持的。
则得票最多的同学所在的SCC一定没有出边(指向其他SCC的边)。
枚举所有出度为0的SCC,如何找出每一个指向这个SCC的其他SCC?从每个点搜索,看其能否到达这个SCC?
更好的方法是,用反图,在反图中求这个SCC能达到的其他SCC,票数就是这些SCC中节点的和,然后减去1(自己给自己的票);
做法:用kosaraju求SCC、在反图中按SCC缩点、枚举入度为0的点、对每个枚举的点搜索
代码:
//求SCC时,假设当前联通块编号为i,如果搜到了编号不同的联通块j,就建立i->j的有向边, //这样生成的是反图,因为是在反图上求SCC #include <cstdio> #include <iostream> #include <vector> #include <cstring> #include <algorithm> using namespace std; const int MAXN=5e3+5; bool existEdge[MAXN][MAXN]; vector<int> G[MAXN],reG[MAXN],cG[MAXN]; //cG=contracted graph int dfn[MAXN],dcnt; //正图dfs的后序序列 dcnt==dfs后序序列计数 int SCC[MAXN],numOfSCC,nodesOfSCC[MAXN]; //SCC[i]=k,表示节点i属于第k个联通块 int N,M; int visited[MAXN]; int inDegree[MAXN]; int ans[MAXN]; void dfs1(int x) { visited[x]=1; for(auto y:G[x]) if(!visited[y]) dfs1(y); dfn[++dcnt]=x; } void dfs2(int x) { SCC[x]=numOfSCC; nodesOfSCC[numOfSCC]++; for(auto y:reG[x]) { int u=numOfSCC,v=SCC[y]; if( v!=0 && u!=v ) //如果y属于另一个联通块,就建立联通块之间的边 { if(existEdge[u][v]==0) { cG[u].push_back(v); existEdge[u][v]=1; inDegree[v]++; //反图中,联通块的入度 } } else if(v==0) dfs2(y); //如果SCC[y]=0,则y一定是当前SCC的点;否则一定是之前已经搜过的其他SCC的点,这是由Kosaraju算法本身决定的 } } int dfs3(int x) { int now=nodesOfSCC[x]; visited[x]=1; for(auto y:cG[x]) if(!visited[y]) now+=dfs3(y); return now; } void kosaraju() { dcnt=0,numOfSCC=0; memset(visited,0,sizeof(visited)); //求正图的后序序列 for(int i=0;i<N;i++) if(!visited[i]) dfs1(i); //用逆后序序列搜反图,求联通块 for(int i=N;i>=1;i--) if(!SCC[dfn[i]]) numOfSCC++,dfs2(dfn[i]); } int main() { // freopen("a.txt","r",stdin); int T; cin>>T; for(int K=1;K<=T;K++) { cin>>N>>M; for(int i=0;i<=N;i++) //注意序号从0开始 G[i].clear(),reG[i].clear(),cG[i].clear(); memset(dfn,0,sizeof(dfn)); memset(SCC,0,sizeof(SCC)); memset(nodesOfSCC,0,sizeof(nodesOfSCC)); memset(visited,0,sizeof(visited)); memset(inDegree,0,sizeof(inDegree)); memset(ans,0,sizeof(ans)); memset(existEdge,0,sizeof(existEdge)); for(int i=1;i<=M;i++) { int u,v; scanf("%d %d",&u,&v); G[u].push_back(v); reG[v].push_back(u); //反图 } kosaraju(); //找反图的缩图中搜入度为0的联通块 vector<int> Nodes; for(int i=1;i<=numOfSCC;i++) if(inDegree[i]==0) Nodes.push_back(i); //在反图的缩图中,从入度为0的联通块开始搜 int maxi=-1; for(auto i:Nodes) { memset(visited,0,sizeof(visited)); ans[i]=dfs3(i)-1; maxi=max(maxi,ans[i]); } vector<int> students; for(int i=1;i<=numOfSCC;i++) { if(ans[i]==maxi) { for(int j=0;j<N;j++) if(SCC[j]==i) students.push_back(j); } } //输出 printf("Case %d: %d ",K,maxi); // sort(students.begin(),students.end()); for(auto x:students) printf( x==students[0] ? "%d" : " %d",x); cout<<endl; } return 0; }