一、基础
1、流图:给定有向图G,如果从G中一个顶点出发可以到达图中所有点,则称G是一个流图,记为(G,r),其中r称为流图的源点
2、强连通图:对一个有向图,如果图中任意两个结点x,y,既存在x到y的路径,也存在y到x的路径,则称该有向图是强连通图
3、强连通分量:有向图的极大连通子图被称为强连通分量,记为SCC
4、追溯值:x的追溯值low[x]定义为满足以下条件的节点的最小的时间戳(通过返祖边能到达的最小的值):
(1)、该点在栈中;(2)、存在一条从以x为根的子树出发的有向边,以该点为终点
计算方法:
(1)、当节点x第一次被访问时,把x入栈,初始化low[x]=dfn[x]
(2)、历遍x出发的每一条边(x,y):
如果y没被访问过,则说明(x,y)是树枝边(搜索树中的边),此时应递归访问y,从y回溯之后,令low[x]=min(low[x],low[y])
如果y被访问过且在栈中,则令low[x]=min(low[x],dfn[y])
(3)、从x回溯之前,判断是否有low[x]=dfn[x],若成立则不断从栈中弹出节点,直至x出栈
二、强连通分量的判定
在追溯值(low)计算过程中,若从x回溯前,由low[x]=dfn[x]成立,则栈中从x到栈顶的所有节点构成一个强连通分量
int head[maxn], to[maxn], nex[maxn], cntt;
int stk[maxn], len, instk[maxn];
int c[maxn], f[maxn]; //第i个点属于第c[i]个强连通分量
int dfn[maxn], low[maxn], tot;
vector<int>scc[maxn];
int cnt;
void add(int u, int v)
{
to[++cntt] = v;
nex[cntt] = head[u];
head[u] = cntt;
}
void dfs(int x)
{
dfn[x] = low[x] = ++tot;
stk[++len] = x;
instk[x] = 1;
for (int i = head[x]; i; i = nex[i])
{
int y = to[i];
if (!dfn[y])
{
dfs(y);
low[x] = min(low[x], low[y]);
}
else if (instk[y])
{
low[x] = min(low[x], low[y]);
}
}
if (dfn[x] == low[x])
{
cnt++;
int y;
do
{
y = stk[len--];
instk[y] = 0;
c[y] = cnt;
scc[cnt].push_back(y);
} while (y != x);
}
}
void solve()
{
int n, m;
cin >> n >> m;
for (int i = 1; i <= m; ++i)
{
int u, v;
cin >> u >> v;
add(u, v);
}
for (int i = 1; i <= n; ++i)
{
if (!dfn[i])
dfs(i);
}
for (int i = 1; i <= cnt; ++i)
sort(scc[i].begin(), scc[i].end());
cout << cnt << endl;
for (int i = 1; i <= n; ++i)
{
if (f[c[i]])
continue;
f[c[i]] = 1;
for (int j = 0; j < scc[c[i]].size(); ++j)
{
cout << scc[c[i]][j] << " ";
}
cout << '\n';
}
}
三、SCC缩点
缩点完成之后会得到一张有向无环图
//在求强连通分量的基础上
int sto[maxn << 1], snex[maxn << 1], shead[maxn], scnt;
void add_scc(int u, int v)
{
sto[++scnt] = v;
snex[scnt] = shead[u];
shead[u] = scnt;
}
//以下代码加在main中
for (int i = 1; i <= n; i++)
{
int y = to[i];
if (c[i] == c[y])
continue;
add_scc(c[i], c[y]);
}