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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章