強連通分量

翰林猿發表於2024-07-07

強連通分量

目錄

  • 基本概念

  • \(Kosaraju\)演算法

  • \(Tarjan\)演算法

  • 例題講解

  • 題目推薦

  • 學習資源


基本概念

  • 連通圖

無向圖中,從任意點\(i\)可以到達任意點\(j\)

  • 強連通圖

有向圖中,從任意點\(i\)可以到達任意點\(j\)

  • 弱連通圖(瞭解即可)

人為地將有向圖看做無向圖後,從任意點\(i\)可以到達任意點\(j\)

  • 極大強連通子圖

\(G\)是一個極大強連通子圖,當且僅當\(G\)是一個強連通子圖且不存在另一個強連通子圖\(G'\),使得\(G\)\(G'\)的真子集

  • 強連通分量

有向非強連通圖的極大強連通子圖

因為來現實生活中有意義的強連通圖很少,所以一般討論的都是強連通分量

若將有向圖中的強連通分量都縮為一個點,則原圖就會變成一個DAG(有向無環圖),如下圖(1)-圖(2)所示:

圖(1)

圖(2)

來講(囉嗦 )一下,因為強連通分量相當於環啊,將環縮為點之後那就是無環圖咯,這個很好想,證明的話反證法即可

  • 強連通分量的應用
  1. 有向圖的縮點:見上圖示

  2. 解決\(2-SAT\)問題(還沒學....之後更新啊qwq)


\(Kosaraju\)演算法

基於兩次\(DFS\)的有向圖強連通分量演算法,時間複雜度為\(O(n+m)\)

  • 演算法框架
  1. 對原圖\(G\)進行\(DFS\),記錄每個節點訪問完的順序\(dfn[i]\)並將點壓入

  2. 選擇最晚訪問完的節點對\(G\)反向圖進行第二次\(DFS\),刪除能夠遍歷到的節點,每次遍歷到的一坨(或一個)節點構成一個強連通分量

  3. 一直執行\((2)\)操作,直到所有節點都二次遍歷完

  • 例子圖示

第一次\(DFS\)順序:\(3->2->1->4\)!)

第二次\(DFS\)順序:\(4,2->1,3\) (一個逗號前為一個強連通分量)

  • 程式碼函式段

個人認為比接下來的\(Tarjan\)好理解,但是使用的更多的還是擴充套件性更強的\(Tarjan\)

inline void dfs1(int x) {
    vis[x]=1;
    for(register int i=1;i<=n;i++) {
        if(!vis[i]&&map[x][i]) dfs1(i);
        dfn[++t]=x;
    }
}

inline void dfs2(int x) {
    vis[x]=t;
    for(register int i=1;i<=n;i++) {
        if(!vis[x]&&map[i][x]) dfs2(i);
    }
}

inline void ko() {
    t=0;
    for(register int i=1;i<=n;i++) {
        if(!vis[i]) dfs1(i);
    }
    memset(vis,0,sizeof(vis));
    t=0;
    for(register int i=n;i>=1;i--) {
        if(!vis[dfn[i]]) {
            t++;
            dfs2(dfn[i]);
        }
    }
}

\(Tarjan\)演算法

基於一次\(DFS\)的演算法,時間複雜度也是\(O(n+m)\)

\(kosaraju\)演算法的\(DFS\)不同,\(Tarjan\)\(DFS\)更類似於樹的後序遍歷

上圖理解吧:


圖片截圖自我的學習視訊,在文章最後會貼上,侵刪!

至於很多部落格講的四種邊(樹枝邊、前向邊、後向邊、橫叉邊),我個人認為是沒有必要掌握的,瞭解一下就可以了

  • 所需變數
  1. \(dfn[i]\):表示節點\(i\)的遍歷順序(同\(Kosaraju\)演算法)

  2. \(low[i]\):表示節點\(i\)可回溯到的最早遍歷時間(初始與\(dfn[i]\)一致)

  3. \(fir[i]=x\):表示節點\(i\)和節點\(x\)同屬於一個強連通分量

  4. \(q[top]\):手寫棧qwq

5. 以及一啪啦的變數(可麻煩了)

  • 演算法框架

設當前點為\(x\)

  1. 初始\(dfn[x]\)=\(low[x]\)=\(++ti\)(時間戳)

  2. 入棧當前點並標記為訪問過

  3. 遍歷與\(x\)相連的點,進行下一層的\(DFS\),然後更新\(low[x]\)

  4. 遍歷完後,如果當前\(x\)\(dfn\)==\(low\),則可以彈出一個強連通分量了

可能有點抽象,如果不好理解,可以先跳到文末點選視訊連結,裡面講得敲詳細qwq

  • 程式碼實現

以下程式碼是根據洛谷P3387 【模板】縮點 這道題編的,大家注意區分啊

另外,下面這份程式碼涉及到的拓撲排序,有興趣的可以看我的另一篇部落格

(PS:變數申請那塊奇醜...輕噴)

#include <bits/stdc++.h>
using namespace std;
int n,m,u,v,tot,ans,a[520010],in[520010],fir[520010],head[520010];
int ti,top,num,q[520010],vis[520010],dis[520010],sum[520010],dfn[520010],low[520010];

struct node {  
	int to,net,fro;
} e[520010],es[520010];

inline void add(int u,int v) {
	e[++tot].to=v;
	e[tot].fro=u;
	e[tot].net=head[u];
	head[u]=tot;
}

inline void tarjan(int x) {
	dfn[x]=low[x]=++ti;
	q[++top]=x;
	vis[x]=1;
	for(register int i=head[x];i;i=e[i].net) {
		int v=e[i].to;
		if(!dfn[v]) {
			tarjan(v);
			low[x]=min(low[x],low[v]);
		}
		else {
			if(vis[v]) low[x]=min(low[x],dfn[v]);
		}
	}
	if(low[x]==dfn[x]) {
		int v=q[top];
		while(top) {
			fir[v]=x;
			vis[v]=0;
			if(v==x) {
				top--;
				break;
			}
			a[x]+=a[v];
			v=q[--top];
		}
	}
}

inline void topo() {  //拓撲排序求最長路
	queue<int> q;
	for(register int i=1;i<=n;i++) {
		dis[i]=a[i];
		if(!in[i]&&fir[i]==i) q.push(i);
	}
	while(!q.empty()) {
		int x=q.front();
		q.pop();
		for(register int i=head[x];i;i=es[i].net) {
			int v=es[i].to;
			dis[v]=max(dis[v],dis[x]+a[v]);
			if(--in[v]==0) q.push(v);
		}
	}
}

int main() {
	scanf("%d%d",&n,&m);
	for(register int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(register int i=1;i<=m;i++) {
		scanf("%d%d",&u,&v);
		add(u,v);
	}
	for(register int i=1;i<=n;i++) {
		if(!dfn[i]) tarjan(i);
	}
	tot=0;
	memset(vis,0,sizeof(vis)); 
	memset(head,0,sizeof(head));
	for(register int i=1;i<=m;i++) {
		u=fir[e[i].fro];
		v=fir[e[i].to];
		if(u!=v) {
			in[v]++;
			es[++tot].to=v;
			es[tot].net=head[u];
			es[tot].fro=u;
			head[u]=tot;
		}
	}
	topo();
	for(register int i=1;i<=n;i++) ans=max(ans,dis[i]);
	printf("%d",ans);
	return 0;
}

例題講解

洛谷P2341 [USACO03FALL][HAOI2006]受歡迎的牛 G

個人認為很模板的題,思路稍微轉換一下就出來了,寫個小題解當做例題講解qwq

  • 分析

奶牛們之間的喜愛是單向的、可傳遞的,那麼將文字描述抽象一下,即:

奶牛們是點,喜愛關係是單向邊,整個關係則構成了有向圖

相互喜愛的一群牛組成一個集合,則\(N\)頭牛可以劃分為\(S\)個集合(縮點的思想,縮環為點

集合與集合之間也存在喜愛關係,則原圖就轉化為了DAG(有向無環圖)

這個時候我們就需要思考一下:到底什麼樣的牛是所有牛喜歡的那個?

顯然:是出度為\(0\)的集合中的牛

為什麼?因為出度為\(0\)則說明這個集合不喜愛其他的牛,則滿足所有牛都喜歡這頭牛(有點繞,畫一下圖會好一點,作者懶不想畫了QAQ)

由此,我們就將問題轉換為了:縮點,然後求出度為\(0\)的點

現在給出以上思路的\(AC\)程式:

#include <bits/stdc++.h>
using namespace std;
int n,m,u,v,tot,num,out[520010],sum[520010],head[520010];
int ti,top,q[520010],val[520010],fir[520010],vis[520010],dfn[520010],low[520010];

struct node {
	int to,net,fro;
} e[520010];

inline void add(int u,int v) {
	e[++tot].to=v;
	e[tot].fro=u;
	e[tot].net=head[u];
	head[u]=tot;
}

inline void tarjan(int x) {
	dfn[x]=low[x]=++ti;
	q[++top]=x;
	vis[x]=1;
	for(register int i=head[x];i;i=e[i].net) {
		int v=e[i].to;
		if(!dfn[v]) {
			tarjan(v);
			low[x]=min(low[x],low[v]);
		}
		else {
			if(vis[v]) low[x]=min(low[x],dfn[v]);
		}
	}
	if(low[x]==dfn[x]) {
		int v=q[top];
		while(top) {
			fir[v]=x;
			vis[v]=0;
			if(v==x) {
				top--;
				break;
			}
			val[x]+=val[v];
			v=q[--top];
		}
	}
}

int main() {
	scanf("%d%d",&n,&m);
	for(register int i=1;i<=n;i++) val[i]=1;
	for(register int i=1;i<=m;i++) {
		scanf("%d%d",&u,&v);
		add(u,v);
	}
	for(register int i=1;i<=n;i++) {
		if(!dfn[i]) tarjan(i);
	}
	for(register int i=1;i<=m;i++) {
		u=fir[e[i].fro];
		v=fir[e[i].to];
		if(u!=v) out[u]++;
	}
	for(register int i=1;i<=n;i++) {
		if(!out[i]&&fir[i]==i) {
			sum[++num]=val[i];
		}
	}
	if(num==1) printf("%d",sum[num]);
	else if(num>=2) puts("0");
	return 0;
}

PS:以下內容為我的無腦暴力騙分程式碼,可以跳過(52\(pts\)真香)

#include <bits/stdc++.h>
using namespace std;
int n,m,u,v,sum,flag;
int in[520010],out[520010];

int main() {
	scanf("%d%d",&n,&m);
	for(register int i=1;i<=m;i++) {
		scanf("%d%d",&u,&v);
		out[u]++;
		in[v]++;
	}
	for(register int i=1;i<=n;i++) {
		if(out[i]==0) sum++;
	}
	if(sum==1) printf("1");
	else if(sum>=2) printf("0");
	else if(sum==0){
		for(register int i=1;i<=n;i++) {
			if(out[i]!=in[i]) {
				flag=true;
				break;
			}
		}
		if(flag==false) printf("%d",n);
	}
	return 0;
}

例題講解(正文)完畢~~


題目推薦

  1. 洛谷P3387 【模板】縮點

  2. 洛谷P2341 [USACO03FALL][HAOI2006]受歡迎的牛 G

  3. 洛谷P4782 【模板】2-SAT問題

  4. 洛谷P1407 [國家集訓隊]穩定婚姻


學習資料

  1. \(B\)站學習視訊,講得真的很清晰,適合初學者

  2. \(Tarjand\)演算法,圖示詳細但有點醜


相關文章