强连通分量
先推荐几道综合性较强的题目,详细题解我写了,持续更新中。
P2341 [USACO03FALL][HAOI2006]受欢迎的牛 G
强连通分量( strongly connected components ),可以理解为存在一条回路使得初始点又能回到初始点。
有两种算法, 这里分享的是 Tarjan (我用前向星存图)。
1. 基本概念:
Tarjan 基于对图深搜的算法,每个强连通分量为搜索树中的一棵子树。 搜索时把没处理的结点加入一个栈,回溯时判断当前结点能否与栈中结点构成一个强连通分量。 (我用数组模拟的栈。)
而我们要做的就是分别找到一条条回路,使得初始点又回到初始点(对于该有向子图,任意两点 G u , v G_u,_v Gu,v 有一条从 u 到 v 的路径,亦有一条从 v 到 u 的路径)。
后面附了图,可以结合示例理解。
2. 数组、栈:
板子一般开两个数组以及一个栈, dfn 数组, low 数组, st 栈。
定义如下:
-
dfn 数组:定义 d f n u dfn_u dfnu 为结点 u 的搜索次序编号(时间戳);
-
low 数组:定义 l o w u low_u lowu 表示结点 u 或者 u 的子树最多能够回溯(到达)到的结点 x , 使得 d f n u dfn_u dfnu 最小;
-
st 栈:每找到一个点就把它压入栈中,这是为了之后找一个强连通分量里所有的子节点。
用法、作用如下:
-
dfn 数组:记录时间戳(编号);
-
low 数组:当一个点最多能回溯(到达)到的点还是自己时,说明这条路径已经成环,根据前面说过的两条定义,这个点以及路径上的节点形成了一个强连通分量。
同时,我们在找强连通分量的过程中,也要不断地用它能到达的节点的 dfn 值去松弛它自己的 dfn 值;
-
st 栈:当我们从点 u 走完了所有可以到达的点后 ,满足 l o w u = d f n u low_u=dfn_u lowu=dfnu 时,我们将栈中的点逐一弹出,解释如下:
我们在第一次找到点 u 时,已经将它入栈了。且我们在经过了若干节点并将它们按先后顺序并分别入栈后,发现从 u 和若干节点成一个强连通分量了。
这时候我们想找到这个强连通分量内所有的节点,我们要怎么做?
逐一从栈中弹出。从栈顶到栈中的 u ,都是我们要的当前强连通分量内的节点(从 u 开始我们都把它们压入栈中了),所以,我们只需要一直把栈顶从栈中弹出,直到弹出点 u 。
3. 算法主要过程:
概述: 通过深度优先搜索和入栈出栈的操作来找到一条回路,使回路初始点又能回到初始点。
-
记录 d f n u dfn_u dfnu 以及 l o w u low_u lowu ;
-
将 u 入栈;
-
前向星( vector 当然也可以)枚举每一条边,更新 l o w u low_u lowu :
-
如果结点 v (枚举到的点)没有被访问过:访问v,如果 l o w v low_v lowv 小于 l o w u low_u lowu ,就用前者更新后者;
-
如果结点 v 仍在栈内即 v 还不属于仍何强连通分量,并用 d f n v dfn_v dfnv 来刷新 l o w u low_u lowu (在前者小于后者的情况下)。
- 如果形成了一条回路,即边弹出栈边统计当前节点的强连通分量编号即可,直到弹出了 u ,此时被弹出的所有节点,就形成了一个强连通分量。
4. 画图示例:
如图所示,在经过如上所说的操作后,一幅图可以被分成若干强连通分量。
示例中就被分成了 3 个强连通分量,可结合强连通分量的定义理解。
以上就是 Tarjan 算法,还是不明白可以见代码或私信问我。
此题其他的操作并不难,这里也不多阐述了。
完整代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn = 100005;
int n, m;
int cnt, hd[maxn];
struct node{
int to, nxt;
}e[maxn * 2];
int dfn[maxn], low[maxn];
int tmp, top;
int st[maxn];
int co[maxn], col;
void add (int u, int v)
{
e[++cnt].to = v;
e[cnt].nxt = hd[u];
hd[u] = cnt;
}
void tarjan (int u)
{
dfn[u] = low[u] = ++tmp;//step 1
st[++top] = u;//step 2
for (int i = hd[u]; i; i = e[i].nxt)//step 3
{
int v = e[i].to;
if (!dfn[v])//如果没有被访问过
{
tarjan (v);
low[u] = min (low[u], low[v]);
}
else if (!co[v]) //如果它还不在一个强连通分量内
low[u] = min (low[u], dfn[v]);
}
if (low[u] == dfn[u])//step 4
{
co[u] = ++col;
while (st[top] != u)//弹栈操作
{
co[st[top]] = col;
--top;
}
--top;//最后记得把 u 也弹出去
}
}
int vis[maxn];
signed main ()
{
scanf ("%lld %lld", &n, &m);
for (int i = 1; i <= m; i++)
{
int u, v;
scanf ("%lld %lld", &u, &v);
add (u, v);
}
for (int i = 1; i <= n; i++)
{
if (!dfn[i]) tarjan (i);
}
printf ("%lld\n", col);
for (int i = 1; i <= n; i++)
{
if (vis[i]) continue;
printf ("%lld ", i);
vis[i] = 1;
for (int j = i + 1; j <= n; j++)
{
if (co[j] == co[i])
{
printf ("%lld ", j);
vis[j] = 1;
}
}
printf ("\n");
}
return 0;
}
—— E n d \mathfrak{End} End——