SCC
SCC就是強連通分量,是有向圖中的一個概念
**強連通:**有向圖G中任意兩個結點聯通
**強連通分量:**極大的強連通子圖
Kosaraju算法
算法的目標就是找到有向圖中所有的SCC
- 先dfs確定原圖的逆後序序列
- 再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;
}