班长竞选(连通分量+缩点)

题意:

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

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

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

样例输入:

2
4 3
3 2
2 0
2 1
3 3
1 0
2 1
0 2

样例输出:

Case 1: 2
0 1
Case 2: 2
0 1 2

思路:

题目分析:
能够想到将同一个连通分量中的点聚集,要计算得票数包括本连通分量中的点及只想该连通分量的其他连通分量中的值。
所以遮体的关键:1)找到图中所有的SCC
2)缩点,确定每个SCC的指向关系

算法实现:
1)用Kosaraju求SCC
第一遍DFS遍历原图,确定原图的后序序列
将后序序列变成逆后序序列,第二遍DFS按照逆后序序列遍历反图,确定所有SCC,遍历过程中记录每个SCC中点的数目。
2)缩点
将同一个SCC的点合并成一个点,遍历反图中的每个点的边,如果边的两端不在同一SCC中则加入该边,最终形成新的缩点后的图。
3)计算票数:
遍历缩点后的图,对于入度为0的点(反图,实际上指向它,即可以给它投票),DFS计算该点总共获得的票数(减去自身)

代码:

#include
#include
using namespace std;

int T,M,N,A,B,tot1,tot2,tot3,dcnt,scnt,r,theMax;
struct edge{
	int v,nxt;
}; 
edge e1[30010],e2[30010],e3[30010];
int head1[5010],head2[5010],head3[5010];
int vis[5010],vis2[5010],d[5010],c[5010];				//d记录后序序列,c记录所在scc编号
int sz[5010],indegree[5010],res[5010];			//记录每个scc中的点的个数 ,每个scc的入度 

void add1(int a,int b){				//加边 
	e1[++tot1].v=b; e1[tot1].nxt=head1[a]; 
	head1[a]=tot1;
}

void add2(int a,int b){				//加边 
	e2[++tot2].v=b; e2[tot2].nxt=head2[a]; 
	head2[a]=tot2;
}

void add3(int a,int b){				//加边 
	e3[++tot3].v=b; e3[tot3].nxt=head3[a]; 
	head3[a]=tot3; indegree[b]++;
}

void init(){
	for(int i=0;i<5010;i++){
		head1[i]=0; head2[i]=0; head3[i]=0;
		vis[i]=0; vis2[i]=0; d[i]=0; sz[i]=0; c[i]=0;
		res[i]=0; indegree[i]=0;
	}
	tot1=0;tot2=0;tot3=0;dcnt=0;scnt=0;
} 

void dfs1(int x){					//确定后序序列 
	vis[x]=1;
	for(int i=head1[x];i;i=e1[i].nxt){
		if(!vis[e1[i].v])  dfs1(e1[i].v);
	}
	d[++dcnt]=x;
}

void dfs2(int x){					//确定后序序列f 
	c[x]=scnt;
	sz[scnt]++;							//该scc中的点增加 
	for(int i=head2[x];i;i=e2[i].nxt){
		if(!c[e2[i].v]) dfs2(e2[i].v); 
	}
}

void dfs3(int x){
	vis2[x]=1;
	r+=sz[x];							//x连通分量指向所求连通分量,加上 
	for(int i=head3[x];i;i=e3[i].nxt)
		if(!vis2[e3[i].v]) dfs3(e3[i].v);
}

void kosaraju(){
	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[d[i]]){
			++scnt; dfs2(d[i]);			//dfs2确定所在连通分量 
		}
}

void shrink(){						//缩点 
	for(int i=0;i<N;i++){
		for(int j=head2[i];j;j=e2[j].nxt){
			int t=e2[j].v;
			if(c[i]==c[t]) continue;		//在一个连通分量中
			add3(c[i],c[t]);					//不在一个,整体连线加边 
		}
	} 
}

int main(){
	scanf("%d",&T);
	for(int i=0;i<T;i++){
		init();
		scanf("%d%d",&N,&M);
		for(int j=0;j<M;j++){
			scanf("%d%d",&A,&B);
			add1(A,B);  add2(B,A);
		}
		kosaraju();
		shrink();
		for(int j=1;j<=scnt;j++){
			if(indegree[j]==0){
				r=0; memset(vis2,0,sizeof(vis2));
				dfs3(j);
				res[j]+=(r-1);					//减去本身 
			}
		}
		theMax=0;
		for(int j=1;j<=scnt;j++)
			if(theMax<res[j]) theMax=res[j];	//确定最大值 
		printf("Case %d: %d\n",i+1,theMax);
		int t_num=0; 
		for(int j=0;j<N;j++){
			if(res[c[j]]==theMax){
				if(t_num==0){printf("%d",j);t_num++;}
				else printf(" %d",j);
			}
		}
		printf("\n");
	}
	return 0;
}

你可能感兴趣的:(程序设计)