有向圖的強連通分量


B3609 [圖論與代數結構 701] 強連通分量

一些概念

  1. 若一張有向圖中任意兩個節點 \(x,y\),存在 \(x\)\(y\) 的路徑和 \(y\)\(x\) 的路徑,則稱其為強連通圖
  2. 有向圖的極大強連通子圖被稱為強連通分量

在上文中,一個強連通子圖 \(G'=(V',E')(V\subseteq V,E'\subseteq E)\) 極大,當且僅當不存在包含 \(G'\) 的更大子圖 \(G''=(V'',E'')\) 滿足 \(V'\subseteq V''\subseteq V,E'\subseteq E''\subseteq E\)。顯然,一個環一定是強連通圖,所以我們的思路就是找到能和某個點構成環的所有點。

正如本文的 URL,強連通分量簡記為 \(\text{SCC(Strongly Connected Component)}\)

\(\text{SCC}\) 可以用 \(\rm Tarjan,Kosaraju\) 或者 \(\rm Garbow\) 算法,本文介紹 \(\rm Tarjan\) 算法。

定義

  • (\(\operatorname{int}\))\(Time\):當前時間戳;
  • (\(\operatorname{int}\))\(tot\)\(\text{SCC}\) 的個數;
  • (\(\operatorname{int}\))\(dfn(u)\):點 \(u\) 的 dfs 序;
  • (\(\operatorname{int}\))\(low(u)\):以下節點的 \(dfn\) 的最小值:\(v\in subtree(u)\)\(u\) 的子樹) 及從 \(v\) 出發通過一條不在搜索樹上的邊(非樹邊)能到達的節點;
  • (\(\operatorname{int}\))\(c(u)\):記錄點 \(u\) 所在的 \(\text{SCC}\)
  • (\(\operatorname{stack}\)<\(\operatorname{int}\)>)\(sta\):一個棧;
  • (\(\operatorname{bool}\))\(ins(u)\):點 \(u\) 是否在 \(sta\) 中;
  • (\(\operatorname{vector}\)<\(\operatorname{int}\)>)\(scc(i)\):記錄編號為 \(i\)\(\text{SCC}\) 內的所有節點。

\(\rm Tarjan\) 算法使用 \(\rm dfs\) 實現:

  1. 記錄 \(dfn,low\)
  2. 當前節點進棧;
  3. 更新 \(low\)
    1. \(dfn(v)=0\):說明 \(v\)\(u\) 的直系兒子,\(v\) 能到的 \(u\) 都能到,先往下遞歸,再直接用 \(low(v)\) 來更新 \(low(u)\)
    2. \(ins(v)=true\):說明 \(v\)\(u\) 的祖先且 \(u\) 可以通過一條非樹邊到達 \(v\),根據定義可以用 \(dfn(v)\) 來更新 \(low(u)\)
  4. 更新后,如果 \(dfn(u)=low(u)\),說明從 \(u\) 出發最終又能回到 \(u\),即構成了環,是一個滿足要求的 \(\text{SCC}\)。將 \(tot\gets tot+1\),同時不停地彈棧直到彈到 \(u\),則彈出的所有節點都在 \(subtree(u)\) 內;記錄其在第 \(tot\)\(\text{SCC}\) 內;最后將其加入 \(scc(tot)\) 中。

對於題目的特殊要求要求:

第一行輸出 \(1\) 號點所在強連通分量,第二行輸出 \(2\) 號點所在強連通分量,若已被輸出,則改為輸出 \(3\) 號點所在強連通分量,以此類推。

開一個 (\(\operatorname{bool}\))\(vis\) 數組記錄每個 \(\text{SCC}\) 是否已經輸出過即可。

每個強連通分量按節點編號大小輸出

\(\operatorname{sort}\) 一遍即可。


\(\text{Code}\)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <stack>
#include <vector>
#define re register
using namespace std;

inline int read()
{
	re int x = 0, f = 0;
	re char c = getchar();
	while (c < '0' || c > '9')
	{
		f |= c == '-';
		c = getchar();
	}
	while (c >= '0' && c <= '9')
	{
		x = (x << 3) + (x << 1) + (c ^ '0');
		c = getchar();
	}
	return f ? -x : x;
}

inline void write(int x)
{
	if (x < 0)
	{
		putchar('-');
		x = -x;
	}
	if (x > 9)
	{
		write(x / 10);
	}
	putchar(x % 10 ^ '0');
}

inline int min2(int x, int y)
{
	return x < y ? x : y;
}
//-----------------------------------------------------------
const int MAXN = 1e4 + 5;
const int MAXM = 1e5 + 5;

int cnt, Time, tot;
int head[MAXN], dfn[MAXN], low[MAXN], c[MAXN];
bool ins[MAXN], vis[MAXN];
stack<int> sta;
vector<int> scc[MAXN];

struct edge
{
	int to, nxt;
}e[MAXM];

void add(int u, int v)
{
	e[++cnt] = edge{v, head[u]};
	head[u] = cnt;
}

void tarjan(int u)
{
	dfn[u] = low[u] = ++Time; //初始化
	sta.push(u); //進棧
	ins[u] = true; //標記
	for (re int i = head[u]; i; i = e[i].nxt)
	{
		int v = e[i].to; //更新
		if (!dfn[v])
		{
			tarjan(v);
			low[u] = min2(low[u], low[v]);
		}
		else if (ins[v])
		{
			low[u] = min2(low[u], dfn[v]);
		}
	}
	if (dfn[u] == low[u]) //構成了環
	{
		tot++;
		int v = 0;
		while (u != v)
		{
			v = sta.top(); //彈棧
			sta.pop();
			ins[v] = false; //取消標記
			c[v] = tot;
			scc[tot].push_back(v); //記錄答案
		}
	}
}

int main()
{
	int n = read(), m = read();
	for (re int i = 1; i <= m; i++)
	{
		int u = read(), v = read();
		add(u, v);
	}
	for (re int i = 1; i <= n; i++)
	{
		if (!dfn[i]) //防止不連通
		{
			tarjan(i);
		}
	}
	write(tot);
	putchar('\n');
	for (re int i = 1; i <= n; i++)
	{
		int x = c[i];
		if (vis[x])
		{
			continue;
		}
		vis[x] = true; //已輸出過
		sort(scc[x].begin(), scc[x].end());
		for (re int i = 0; i < scc[x].size(); i++)
		{
			write(scc[x][i]);
			putchar(' ');
		}
		putchar('\n');
	}
	return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM