Week8 作业 C - 班长竞选 HDU - 3639

题目描述:

大学班级选班长,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\n",K,maxi);
	//	sort(students.begin(),students.end());
		for(auto x:students)
			printf( x==students[0] ? "%d" : " %d",x);
		cout<<endl;
	}
	return 0;
}

  

 

posted @ 2020-04-17 16:01  菜鸡今天学习了吗  阅读(115)  评论(0编辑  收藏  举报