SDU 程序设计思维与实践 Week8 C 班长竞选【SCC(Kosaraju)、缩点】

C 班长竞选

题意描述

大学班级选班长,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开始,紧跟着是最高的票数。 接下来一行输出得票最多的同学的编号,用空格隔开,行末不要有空格。

输入样例

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

相关概念

  • SCC(Strong Connected Component):极大的强连通子图
  • DFS序分类:
    (1)前序:第一次到达点x的次序
    (2)后续:x点遍历完成的次序,即回溯时间
    (3)逆后序:后续的逆序
  • Kosaraju:用于找到图中所有的SCC
    (1)第一遍DFS确定原图的逆后序序列
    (2)第二遍DFS在反图中按照逆后序序列进行遍历,每次由起点遍历到的点即构成一个SCC

Kosaraju模板

#include<iostream>
#include<vector>
#include<cstring>
using namespace std;
const int N=5005,M=30005;
int n,c[N],dfn[N],vis[N],dcnt,scnt;
//dcnt-dfs序计数,scnt-scc计数
//dfn[i]-dfs后序列中第i个点
//c[i]-i号点所在ssc编号
vector<int> G1[N],G2[N];//G1-原图,G2-反图
void dfs1(int x){
	vis[x]=1;
	for(int i=0;i<G1.size();i++){
		int y=G1[i];
		if(!vis[y]) dfs1(y); 
	}
	dfn[++dcnt]=x;
}
void dfs2(int x){
	c[x]=scnt;
	for(int i=0;i<G2.size();i++){
		int y=G2[i];
		if(!c[y]) dfs(y); 
	}
}
void kosaraju(){
	//初始化
	dcnt=scnt=0;
	memset(c,0,sizeof(c));
	memset(vis,0,sizeof(vis));
	//第一遍dfs
	for(int i=1;i<=n;i++){
		if(!vis[i]) dfs1(i);
	} 
	//第二遍dfs
	for(int i=n;i>=1;i--){
		if(!c[dfn[i]]) ++scnt,dfs2(dfn[i]);
	} 
} 

思路分析

  • 利用Kosaraju求SCC
  • 缩点
  • 对于属于第 i 个 SCC 的点来说,答案分为两部分,令 SCC[i] 表示第 i 个 SCC 中点的个数
    • 当前 SCC 中的点,ans += SCC[i] – 1(去除自己)
    • 其它 SCC 中的点,SUM ( SCC[ j] ),其中 j 可到达 I
  • 最后答案一定出现在出度为 0 的 SCC中
  • 将边反向,对每个入度为 0 的点进行 dfs,计算其能到达的点的SUM(SCC[ j])

代码

#include<iostream>
#include<vector>
#include<cstring>
#include<set>
#include<map>
using namespace std;
const int N=5005,M=30005;
int n,c[N],dfn[N],vis[N],dcnt,scnt,head1[N],head2[N],head3[M],
scc[N],in_deg[N],tot1,tot2,tot3,ans[N],maxans,vis2[N],m,T,anscnt;
struct Edge{
	int to,next,w;
};
set<pair<int,int>> s;
map <int,int> ansno;
Edge e1[M],e2[M],e3[M],e4[M]; 
//dcnt-dfs序计数,scnt-scc计数
void add1(int x,int y){
	e1[++tot1].to=y;
	e1[tot1].next=head1[x];
	head1[x]=tot1;	
}
void add2(int x,int y){
	e2[++tot2].to=y;
	e2[tot2].next=head2[x];
	head2[x]=tot2;
	
}
void add3(int x,int y){
	e3[++tot3].to=y;
	e3[tot3].next=head3[x];
	head3[x]=tot3;	
	in_deg[y]++;	
}
void dfs1(int x){
	vis[x]=1;
	for(int i=head1[x];i;i=e1[i].next){
		int y=e1[i].to;
		if(!vis[y]) dfs1(y); 
	}	
	dfn[++dcnt]=x;//dfn[i]-dfs后序列中第i个点
	
}
void dfs2(int x){
	c[x]=scnt;//c[i]-i号点所在ssc编号
	scc[scnt]++;
	for(int i=head2[x];i;i=e2[i].next){
		int y=e2[i].to;
		if(!c[y]) dfs2(y); 
	}
}
void kosaraju(){
	//初始化
	dcnt=scnt=0;
	memset(c,0,sizeof(c));
	memset(vis,0,sizeof(vis));
	//第一遍dfs
	for(int i=0;i<n;i++){
		if(!vis[i]) dfs1(i);
	} 
	//第二遍dfs
	for(int i=n;i>=1;i--){
		if(!c[dfn[i]]) ++scnt,dfs2(dfn[i]);
	} 
}
void dfs3(int x,int s){
	vis2[x]=1;		
	for(int i=head3[x];i;i=e3[i].next){
		int y=e3[i].to;
		if(!vis2[y]){
			vis2[y]=1;			
			ans[s]+=scc[y];
			dfs3(y,s);
		}		
	}
}
void init(){
	tot1=tot2=tot3=0;
	s.clear();ansno.clear();
	memset(head1,0,sizeof(head1));
	memset(head2,0,sizeof(head2));
	memset(head3,0,sizeof(head2));
	memset(scc,0,sizeof(scc));
	memset(in_deg,0,sizeof(in_deg));
	memset(ans,0,sizeof(ans));
	maxans=0;
	anscnt=0;
} 
int main(){
	cin>>T;
	for(int z=1;z<=T;z++){
		cin>>n>>m;
		init();
		int u,v;	
		while(m--){
			cin>>u>>v;
			add1(u,v);
			add2(v,u);
		}
		kosaraju();	
			
		//缩点、反向图 
		for(int i=0;i<n;i++){
			for(int j=head1[i];j;j=e1[j].next){
				int y=e1[j].to;
				if(c[i]!=c[y]){
				s.insert({c[y],c[i]});				
				}
			}
		}
		for(auto &x:s){
			add3(x.first,x.second);
		}	
		for(int i=1;i<=scnt;i++){
			if(in_deg[i]==0){
				memset(vis2,0,sizeof(vis));			
				ans[i]+=scc[i]-1;
				dfs3(i,i);
			}
		}
		for(int i=1;i<=scnt;i++){
			if(maxans<ans[i]) maxans=ans[i];
		}
		for(int i=1;i<=scnt;i++){
			if(ans[i]==maxans) {
				ansno[i]=1;
			}
		}
		cout<<"Case "<<z<<": "<<maxans<<endl;
		for(int i=0;i<n;i++){			
			if(ansno[c[i]]){
				if(anscnt==0){
					cout<<i;anscnt++;	
				}
				else cout<<" "<<i;
			} 
		}
		cout<<endl;		
	}	
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值