Kosarju模拟:SCC + 例题-班长竞选(缩点)

SCC

SCC就是强连通分量,是有向图中的一个概念

**强连通:**有向图G中任意两个结点联通
**强连通分量:**极大的强连通子图

Kosaraju算法

算法的目标就是找到有向图中所有的SCC

  1. 先dfs确定原图的逆后序序列
  2. 再dfs在反图中(边的方向全部改变)按照逆后序序列进行遍历,寻找出所有的SCC

原理是原图和反图具有一样的SCC

在这里插入图片描述

班长竞选

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

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

对于每组数据,第一行输出 “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

思路:

  • 将每一个人看成是一个点,利用支持关系可以构成一张有向图。由于观点具有传递性,所以如果一个SCC中的某个人支持了SCC外的一个人A,那么相当于整个SCC中的人都支持A
  • 所以本题可以使用Kosarju算法来首先找到SCC,对于反图来说,答案肯定存在于入度为0的SCC中
  • 遍历图中所有边,当一条边的两个端点不存在于同一个SCC中的时候,说明这条边就是两个SCC之间的连接边,通过边的关系可以计算每一个SCC的出度(也就是反图的入度)
  • 首先遍历所有点找到入度为0的SCC,确定答案存在于这个SCC中,再次遍历这个SCC能到达的所有SCC,将能遍历到的SCC的点数都加入到答案中
  • 再次遍历选定的SCC,加上SCC中点的个数-1(个人不能投自己)
  • 需要注意输出的格式

代码:
-这是第一版的代码,但是只能过c++编译器,在g++上运行会出现TLE的问题。发掘应该是点和边的数组都默认使用了最大值3e5来开,浪费了时间

#include<iostream>
#include<algorithm>
#include<vector>
#include<cstring>
#include<cmath>
using namespace std;
const int MN=3e5+1;
int N,M;
struct Edge
{
	int u,v,next;
}e1[MN],e2[MN];//正图反图 
int head1[MN],head2[MN],dfn[MN];
int scnt,c[MN],scc[MN],indeg[MN];//scnt连通子图数量,c[i]第i号处于c[i]号SCC中 
int dcnt,tot; 
//vis-子图中的点是否被访问到,visit- SCC是否被访问到 
bool vis[MN],visit[MN];
int ans[MN];
void add(int u,int v)//tot- 图中边的总数量 
{ 
	e1[tot].u=u;
	e1[tot].v=v;   
	e1[tot].next=head1[u];
	head1[u]=tot; 
	e2[tot].u=v; 
	e2[tot].v=u;   
	e2[tot].next=head2[v];
	head2[v]=tot; 
	tot++;
}
void dfs1(int x)
{
	vis[x]=true;
	for(int i=head1[x];i!=-1;i=e1[i].next)
	{
		if(!vis[e1[i].v]) 
		dfs1(e1[i].v); 
    }
	dfn[dcnt++]=x;
}
void dfs2(int x)
{
	c[x]=scnt; 
	scc[scnt]++;
	for(int i=head2[x];i!=-1;i=e2[i].next)
	{
	 if(!c[e2[i].v]) 
	 	dfs2(e2[i].v);	
	}
}
void dfs3(int x,int j)
{
   vis[x]=true; 
   if(!visit[c[x]]) 
   ans[j]+= scc[c[x]]; 
   visit[c[x]]=true;
   for(int i=head2[x];i!=-1;i=e2[i].next)//开始找SCC中所有点 
   {
   	 if(!vis[e2[i].v]) 
		dfs3(e2[i].v,j);
   }
}
void kosaraju()
{
	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]);
		}
	}
}
int main(){
	int T; 
	scanf("%d",&T); 
	for(int i=1;i<=T;i++)
	{
	    tot=0;
		memset(head1,-1,sizeof(head1));
		memset(head2,-1,sizeof(head2));
		scanf("%d%d",&N,&M);
		while(M--)
		{
			int a,b; 
			scanf("%d%d",&a,&b);
			add(a,b);
		}
		printf("Case %d:",i);
		dcnt=scnt=0;
		memset(scc,0,sizeof(scc));
		memset(vis,false,sizeof(vis));
		memset(c,0,sizeof(c)); 
		memset(indeg,0,sizeof(indeg)); 
		memset(ans,-1,sizeof(ans));
		kosaraju(); 
		for(int i=0;i<M;i++)
		{
		  int tu=e1[i].u,tv=e1[i].v;//原图中的起点和终点 
		  if(c[tu]!=c[tv])//所处连通子图不同->边是连接边 
		  	indeg[c[tu]]++; //对应原图的起点是返图的终点,indeg++ 
		} 
	    int j=0,tic=-1;
		for(int i=0;i<N;i++)
		{
	    	j=c[i];//开始找SCC 
			if(indeg[j]==0)//入度为零的点就是答案 
			{
			  memset(vis,false,sizeof(vis));
			  memset(visit,false,sizeof(visit));	
		      dfs3(i,j); //点,所属scc 
			  indeg[j]=1;
			  tic=max(tic,ans[j]);
			}
		}
		printf(" %d\n",tic); 
		int flag=0;
		for(int i=0;i<N;++i)
		{
			if(ans[c[i]]==tic)
			{
			 	if(flag==0)
			 	{
			 		printf("%d",i);
			 		flag++;
				}
				else printf(" %d",i);
			} 
		} 
		printf("\n");
	}
	return 0;
}
  • 对于点的最大范围进行重置-NN,成功使用g++ AC
#include<iostream>
#include<algorithm>
#include<vector>
#include<cstring>
#include<cmath>
using namespace std;
const int NN=5001; 
const int MN=3e5+1;
int N,M;
struct Edge
{
 int u,v,next;
}e1[MN],e2[MN];//正图反图 
int head1[NN],head2[NN],dfn[NN];
int scnt,c[NN],scc[NN],indeg[NN];//scnt连通子图数量,c[i]第i号处于c[i]号SCC中 
int dcnt,tot; 
//vis-子图中的点是否被访问到,visit- SCC是否被访问到 
bool vis[NN],visit[NN];
int ans[NN];
void add(int u,int v)//tot- 图中边的总数量 
{ 
 e1[tot].u=u;
 e1[tot].v=v;   
 e1[tot].next=head1[u];
 head1[u]=tot; 
 e2[tot].u=v; 
 e2[tot].v=u;   
 e2[tot].next=head2[v];
 head2[v]=tot; 
 tot++;
}
void dfs1(int x)
{
 vis[x]=true;
 for(int i=head1[x];i!=-1;i=e1[i].next)
 {
  if(!vis[e1[i].v]) 
  dfs1(e1[i].v); 
    }
 dfn[dcnt++]=x;
}
void dfs2(int x)
{
 c[x]=scnt; 
 scc[scnt]++;
 for(int i=head2[x];i!=-1;i=e2[i].next)
 {
  if(!c[e2[i].v]) 
   dfs2(e2[i].v); 
 }
}
void dfs3(int x,int j)
{
   vis[x]=true; 
   if(!visit[c[x]]) 
   ans[j]+= scc[c[x]]; 
   visit[c[x]]=true;
   for(int i=head2[x];i!=-1;i=e2[i].next)//开始找SCC中所有点 
   {
     if(!vis[e2[i].v]) 
  dfs3(e2[i].v,j);
   }
}
void kosaraju()
{
 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]);
  }
 }
}
int main(){
 int T; 
 scanf("%d",&T); 
 for(int i=1;i<=T;i++)
 {
     tot=0;
  memset(head1,-1,sizeof(head1));
  memset(head2,-1,sizeof(head2));
  scanf("%d%d",&N,&M);
  while(M--)
  {
   int a,b; 
   scanf("%d%d",&a,&b);
   add(a,b);
  }
  printf("Case %d:",i);
  dcnt=scnt=0;
  memset(scc,0,sizeof(scc));
  memset(vis,false,sizeof(vis));
  memset(c,0,sizeof(c)); 
  memset(indeg,0,sizeof(indeg)); 
  memset(ans,-1,sizeof(ans));
  kosaraju(); 
  for(int i=0;i<M;i++)
  {
    int tu=e1[i].u,tv=e1[i].v;//原图中的起点和终点 
    if(c[tu]!=c[tv])//所处连通子图不同->边是连接边 
     indeg[c[tu]]++; //对应原图的起点是返图的终点,indeg++ 
  } 
     int j=0,tic=-1;
  for(int i=0;i<N;i++)
  {
      j=c[i];//开始找SCC 
   if(indeg[j]==0)//入度为零的点就是答案 
   {
     memset(vis,false,sizeof(vis));
     memset(visit,false,sizeof(visit)); 
        dfs3(i,j); //点,所属scc 
     indeg[j]=1;
     tic=max(tic,ans[j]);
   }
  }
  printf(" %d\n",tic); 
  int flag=0;
  for(int i=0;i<N;++i)
  {
   if(ans[c[i]]==tic)
   {
     if(flag==0)
     {
      printf("%d",i);
      flag++;
    }
    else printf(" %d",i);
   } 
  } 
  printf("\n");
 }
 return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值