kosaraju---班长竞选

文章目录

  • 题意
  • 输入
  • 输出
  • 思路
  • 总结
  • 代码

题意

大学班级选班长,N 个同学均可以发表意见 若意见为 A B 则表示 A 认为 B 合适,意见具有传递性,即 A 认为 B 合适,B 认为 C 合适,则 A 也认为 C 合适 勤劳的 TT 收集了M条意见,想要知道最高票数,并给出一份候选人名单,即所有得票最多的同学,你能帮帮他吗?

输入

本题有多组数据。第一行 T 表示数据组数。每组数据开始有两个整数 N 和 M (2 <= n <= 5000, 0

输出

对于每组数据,第一行输出 “Case x: ”,x 表示数据的编号,从1开始,紧跟着是最高的票数。 接下来一行输出得票最多的同学的编号,用空格隔开,不忽略行末空格!

思路

转化成图论问题
求一个人的票数可以转化为有多少点可以到达该点,因为该图可能存在环,所以不能用拓扑,因此,可以用kosaraju求出强连通分量SCC,进行缩点,然后变成一个无环的图
缩点后,则对于属于第 i 个 SCC 的点来说,答案分为
两部分,令 SCC[i] 表示第 i 个 SCC 中点的个数
• 当前 SCC 中的点,ans += SCC[i] – 1(去除自己)
• 其它 SCC 中的点
• SUM ( SCC[ j] ),其中 j 可到达 I
还有用反证法可以发现最后答案一定出现在出度为 0 的 SCC,
因此 在反图中可以用dfs求出可以到达的点

总结

kosaraju
//求强连通SCC//在有向图中只有SCC中两两互达
第一遍dfs确定原图的逆后序序列
第二遍dfs 在反图中按照逆后序序列进行遍历
每次由起点遍历到的点即构成一个SCC
缩点后将变成一个无环图比较好处理

代码

#include
#include
#include
#include
using namespace std;
int dcnt,scnt,count,n,m;
vector<int>ve[5010],ve1[5010],su[5010];
int SCC[5010],dfn[5010],c[5010],degree[5010],ans[5010];bool vis[5010];
void dfs1(int x)
{
	vis[x]=1;
	for(int i=0;i<ve[x].size();i++)
	{
		if(!vis[ve[x][i]])dfs1(ve[x][i]);
	}
	dfn[dcnt++]=x;//后序序列 
} 
void dfs2(int x)//反图 
{
	c[x]=scnt;SCC[scnt]++;//被标记(同一个强连通标记成scnt从1开始 
	for(int i=0;i<ve1[x].size();i++)
	{if(!c[ve1[x][i]]) dfs2(ve1[x][i]);
	 } 
}
void dfs3(int x)
{    vis[x]=1;count=count+SCC[x];
     for(int i=0;i<su[x].size();i++)
     if(!vis[su[x][i]])dfs3(su[x][i]);
	
}
void kosaraju()
{
	memset(vis,0,sizeof(vis));
	memset(c,0,sizeof(c));
	memset(SCC,0,sizeof(SCC));
	memset(dfn,0,sizeof(dfn));
	dcnt=scnt=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]);
	//scnt为scc个数 	
}
//有的题目中可以考虑反图的作用
//c[i]表示i所在的SCC编号
int main()
{
	int t;scanf("%d",&t);int num=1;
	while(t--)
	{   
		scanf("%d%d",&n,&m);memset(degree,0,sizeof(degree));
		for(int i=0;i<n;i++)
		while(ve[i].size()) ve[i].pop_back();
		for(int i=0;i<n;i++)
		while(ve1[i].size()) ve1[i].pop_back();
		for(int i=0;i<n;i++)
		while(su[i].size()) su[i].pop_back();
		for(int i=1;i<=m;i++)
		{
			int a,b;scanf("%d%d",&a,&b);
			ve[a].push_back(b);
			ve1[b].push_back(a);//反图 
		} kosaraju();//求强连通 
		for(int i=0;i<n;i++)//缩点 
        {
	    	for(int j=0;j<ve1[i].size();j++)
	       {
	     	if(c[i]==c[ve1[i][j]]) continue;
		    su[c[i]].push_back(c[ve1[i][j]]);//再反图缩点 //缩点后点变为1-scnt 
	       }
        }
        for(int i=1;i<=scnt;i++)//记录入度 
        for(int j=0;j<su[i].size();j++)
        degree[su[i][j]]++;memset(ans,0,sizeof(ans));
        int mmax=0;
        for(int i=1;i<=scnt;i++)//用ans来记录每个点可以到达的点数 
        if(!degree[i])//入度为0的点 
        {    memset(vis,0,sizeof(vis));
        	 count=-1;dfs3(i);//count就是一个SCC中的点的可以到达的点 
			 mmax=max(mmax,count);//mmax就是最高票数 
			 for(int j=0;j<n;j++)
			 {
			 	if(c[j]==i) ans[j]=count;
			 }
		}  cout<<"Case"<<' '<<num<<": ";cout<<mmax<<endl;bool flag=0;
		   for(int i=0;i<n;i++)
		   {
		   	if(ans[i]==mmax) 
		   	{
		   		if(!flag)cout<<i;
		   		else cout<<" "<<i;flag=1;
			   }    
		   } cout<<endl;
		   num++;			
	}
}
 

 

你可能感兴趣的:(c++)