Wek8 C-SCC

问题描述:

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

Input:
本题有多组数据。第一行 T 表示数据组数。每组数据开始有两个整数 N 和 M (2 <= n <= 5000, 0 <m <= 30000),接下来有 M 行包含两个整数 A 和 B(A != B) 表示 A 认为 B 合适。

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

Sample Input:
2
4 3
3 2
2 0
2 1


3 3
1 0
2 1
0 2

Sample Output:
Case 1: 2
0 1
Case 2: 2
0 1 2

解题思路:

Kosaraju算法:
①第一遍 dfs 确定原图的逆后序序列
②第二遍 dfs 在反图中按照逆后序序列进行遍历,每次由起点遍历到的点即构成一个 SCC,为了求解该题,还有一个数组cnt[i用来记录每个SCC里的顶点数。
这样就求出了所有的强连通分量,由题意,要选择投票数最高的,且投票具有传递性,且每一个SCC里的点投票数是相同的,所以得票数最高的SCC必定是原图中出度为0的SCC,这里先缩点后再求解。因为要在反图中DFS,所以原图和反图都要存下来,至于求解得票数最高的SCC可以在缩点后,原图中求出度为0的SCC,或者在反图中求入度为0的SCC,我采用了后者,因为前者也还要在反图中DFS求得票数。
思路:
缩点后,不难发现对于属于第 i 个 SCC 的点来说,答案分为两部分,令 SCC[i] 表示第 i 个 SCC 中点的个数:
①当前 SCC 中的点,ans += SCC[i] – 1(去除自己)
其它 SCC 中的点
②SUM ( SCC[j] ),其中 j 可到达 i
缩点后的反图、原图、反图都用链式前向星存储,反图中SCC入度为0的点可能不止一个,所以得到了所有入度为0的SCC后,要对这些SCC进行DFS,sum[i]记录入度为0的SCC最终的得票数,遍历数组sum,就可以得到最高票数,之后对sum[i]为最高票数的SCC里的点进行输出即可。

实验代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;

const int N=5050;
int head1[N],head2[N],head[N];		
int n,c[N],scnt,dcnt,dfn[N],cnt[N];	
//dcnt-dfs序计数,scnt-scc计数,dfn[i]-dfs中后序序列第i个点,c[i]-i号点所在scc编号,cnt第i个子图的元素数 
bool vis[N];				
int sum[N],ans[N],ansize;
int last[N],lcot;
struct Edge
{
	int to;
	int next;
};
Edge edge1[30050]; 			//+
Edge edge2[30050]; 			//-
Edge edge[30050];			
int in_scc[N];				 
int ecnt1,ecnt2,ecnt;
void insert_edge(int a,int b)		
{
	edge1[ecnt1].to=b;
	edge1[ecnt1].next=head1[a];
	head1[a]=ecnt1++;
	edge2[ecnt2].to=a; 
	edge2[ecnt2].next=head2[b];
	head2[b]=ecnt2++;
}
void initial()
{
	ecnt1=0;
	ecnt2=0;
	ecnt=0;
	for(int i=0;i<=n;i++)		
	{
		head1[i]=-1; 
		head2[i]=-1;
		head[i]=-1;
	}
}
void dfs1(int x)
{
	vis[x]=1;
	int y;
	for(int i=head1[x];i!=-1;i=edge1[i].next)
	{
		y=edge1[i].to;
		if(!vis[y])
			dfs1(y);
	}
	dfn[++dcnt]=x;		
} 
void dfs2(int x)
{
	c[x]=scnt;
	cnt[scnt]++;
	int y;
	for(int i=head2[x];i!=-1;i=edge2[i].next)
	{
		y=edge2[i].to;
		if(!c[y])
			dfs2(y);
	}
}
void suodian()
{
	ecnt=0;		
	for(int i=0;i<=scnt;i++)		
		in_scc[i]=0;		
	int j;
	for(int i=0;i<n;i++)
	{ 
		for(int x=head2[i];x!=-1;x=edge2[x].next)
		{
			j=edge2[x].to;		
			if(c[i]!=c[j])		
			{
				edge[ecnt].to=c[j];
				edge[ecnt].next=head[c[i]];
				head[c[i]]=ecnt++;		
				in_scc[c[j]]++; 
			}
		}
	} 
}
void dfs_scc(int x,int k)		 
{
	int y;	
	vis[x]=1;	
	for(int i=head[x];i!=-1;i=edge[i].next)
	{
		y=edge[i].to;
		if(!vis[y])
		{
			sum[k]+=cnt[y];		
			dfs_scc(y,k);
		}
	} 
} 
void kosaraju()
{
	//初始化 
	dcnt=scnt=0;
	for(int i=0;i<=n;i++)
	{
		c[i]=0;
		vis[i]=0;
		cnt[i]=0;
	}			
	for(int i=0;i<n;i++)
		if(!vis[i]) dfs1(i);
	for(int i=n;i>=1;i--)
		if(!c[dfn[i]]) ++scnt,dfs2(dfn[i]);
	suodian();
	int ansize=0;	
	for(int i=1;i<=scnt;i++)
	{
		if(in_scc[i]==0)		
			ans[ansize++]=i;		
	}
	int scc;
	for(int i=0;i<ansize;i++)
	{
		scc=ans[i];			
		sum[i]=cnt[scc]-1;	
		for(int j=0;j<=scnt;j++)
			vis[j]=0;
		dfs_scc(scc,i);
	}			
	int piao=0;
	for(int i=0;i<ansize;i++)
		if(sum[i]>piao)	piao=sum[i];
	cout<<piao<<endl;
	lcot=0;
	for(int i=0;i<ansize;i++)
	{
		if(sum[i]==piao)
			for(int j=0;j<n;j++)
			{
				if(c[j]==ans[i])			
				{
					last[lcot++]=j;
				}
			}
	}
	sort(last,last+lcot); 
	for(int i=0;i<lcot;i++)
	{
		if(i<lcot-1)
			cout<<last[i]<<" ";
		else
			cout<<last[i]<<endl;
	}
}
int main(void)
{
	int T,m;			
	scanf("%d",&T);
	int a,b;
	int cot=0;
	for(int i=0;i<T;i++)
	{
		scanf("%d%d",&n,&m);
		initial();
		for(int i=0;i<m;i++)
		{
			scanf("%d%d",&a,&b);
			insert_edge(a,b); 
		}
		cot++;
		cout<<"Case "<<cot<<": ";
		kosaraju();
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值