题意:
大学班级选班长,n个同学均可以发表意见。若意见为A B,则表示A认为B合适。意见具有传递性,即A认为B合适,B认为C合适,则A也认为C合适。共m条意见,要求出最高票数和候选人名单。
思路:
有向图,有强联通分量SCC,应先求出所有SCC并缩点。求SCC用kosaraju算法,第一遍dfs1确定原图的逆后序序列,第二遍dfs2在反图中按照逆后序序列进行遍历,每次由起点遍历到的点即构成一个SCC。
缩点:求完SCC后,此时有scnt个SCC,且c[i]为顶点i所在的SCC编号。结构point里有一个vector,用来存放该SCC里的所有顶点。point P[scnt]就存储了所有SCC的顶点。用vector G3来存放scc图,且边为反向(之后有用)。注意对边(u,v)添加(c[u],c[v])时,要保证c[u]!=c[v],即两个点不在同一个SCC里。同时要注意两个scc顶点间可能会有多条边,要通过vector的unique函数来去重。
缩点后不难发现对于属于第i个SCC的点来说,答案分为两部分,当前SCC中的点除去自己和其他SCC中的点,即ans=SCC[i]-1+sum(SCC[j]),其中j可到达i。稍加思考,可以发现最后答案一定出现在出度为0的SCC中。所以将G3图中的边反向,对每个入度为0的点进行dfs3,计算其能到达的点SUM(SCC[j]),即可得到答案。
总结:
一道综合性很强的题,要求SCC,再缩点,然后dfs,最后答案输出还要略加注意。还应注意这n个同学的编号为0~n-1。
代码:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
//注意n个同学编号为0~n-1
int n,m,a,b;
vector<int> G1[5010],G2[5010]; //原图与反图
int c[5010],dfn[5010],vis[5010],dcnt,scnt;
//c[i] i号点所在SCC编号;dfn[i] dfs后序列中第i个点
//dcnt dfs序计数;scnt scc计数
void dfs1(int x)
{
vis[x]=1;
for(int i=0; i<G1[x].size(); i++)
if(!vis[G1[x][i]])
dfs1(G1[x][i]);
dfn[dcnt]=x;
dcnt++;
}
void dfs2(int x)
{
c[x]=scnt;
for(int i=0; i<G2[x].size(); i++)
if(!c[G2[x][i]])
dfs2(G2[x][i]);
}
void kosaraju() //求SCC
{
dcnt=0,scnt=0;
for(int i=0; i<5010; i++)
c[i]=0,vis[i]=0;
for(int i=0; i<n; i++)
if(!vis[i])
dfs1(i);
for(int i=n-1; i>=0; i--)
if(!c[dfn[i]])
++scnt,dfs2(dfn[i]);
}
//scc顶点编号从1开始
struct point //scc缩点
{
vector<int> allV;
};
point P[5010]; //用于存放scc的点
vector<int> G3[5010]; //存放scc间的关系,且边为反向
int indeg[5010]; //入度
int dfs3(int v) //计算入度为0的点的sum(scc[j])
{
vis[v]=1;
int ans=P[v].allV.size();
for(int i=0;i<G3[v].size();i++)
if(!vis[G3[v][i]])
ans=ans+dfs3(G3[v][i]);
return ans;
}
int main()
{
ios::sync_with_stdio(false);
int T;
cin>>T;
for(int sjzs=1;sjzs<=T;sjzs++)
{
cin>>n>>m;
//图初始化
for(int i=0; i<n; i++)
G1[i].clear(),G2[i].clear();
//读入图
for(int i=0; i<m; i++)
{
cin>>a>>b;
G1[a].push_back(b);
G2[b].push_back(a);
}
//求SCC
kosaraju();
//缩点
int sum[scnt+1];
for(int i=1;i<=scnt;i++)
G3[i].clear(),indeg[i]=0,sum[i]=0,P[i].allV.clear();
for(int i=0;i<n;i++)
{
P[c[i]].allV.push_back(i);
for(int j=0;j<G1[i].size();j++)
{
if(c[i]!=c[G1[i][j]]) //两点不在同一个scc
{
G3[c[G1[i][j]]].push_back(c[i]);
indeg[c[i]]++;
}
}
}
//G3图应去重
for(int i=1;i<=scnt;i++)
{
sort(G3[i].begin(),G3[i].end());
G3[i].erase(unique(G3[i].begin(),G3[i].end()),G3[i].end());
}
for(int i=1;i<=scnt;i++)
{
if(indeg[i]==0)
{
//清空vis
for(int j=1;j<=scnt;j++) vis[j]=0;
sum[i]=dfs3(i)-1; //自己的点数要-1
}
}
//输出
int maxpiao=0;
for(int i=1;i<=scnt;i++)
if(maxpiao<sum[i]) maxpiao=sum[i];
int ans[5010],index=0;
for(int i=1;i<=scnt;i++)
{
if(sum[i]==maxpiao) //将scci加入到答案序列ans中
{
for(int j=0;j<P[i].allV.size();j++)
{
ans[index]=P[i].allV[j];
index++;
}
}
}
sort(ans,ans+index);
cout<<"Case "<<sjzs<<": "<<maxpiao<<endl;
for(int i=0;i<index-1;i++)
cout<<ans[i]<<" ";
cout<<ans[index-1]<<endl;
}
}