图论连通性简记
无向图双联通分量
在无向图中跑一棵 dfs 生成树,然后对于每个点维护 dfn 和 low,分别表示这个点被访问的时间戳以及其子树中所内回溯到的时间戳最小的节点。
割边定义:将这条边删除后原图的联通块数增加。
割边的判定:若 \(u\) 的儿子 \(v\) 满足 \(v\) 不通过这条边的 low 值 \(>dfn_u\) 则这条边为割边。
割点定义:将这个点删除后原图的联通块数增加。
割点的判定:若这点 \(u\) 为根且儿子数量 \(>1\),那么为割点,否则若存在一个儿子节点 \(v\) 满足 \(low_v\ge dfn_u\),那么这个点为割点。
证明显然。
点双联通分量("v-Dcc")的定义:这个图不存在割点。
点双联通分量的充要条件:任意两个点都存在至少了两条除了起点、终点的点不重复的路径。
点双联通分量缩点:把极大的点双联通分量缩成一个点,这要就可以把图变成树。
点双联通分量的性质:对于任意的 \(u,v,w\) 都存在一条从 \(u\) 到 \(v\) 再到 \(w\) 的简单路径。
求法:维护一个栈,表示当前访问的点,当遇到割点 \(x\) 和子节点 \(y\) 时,把栈弹出直到 \(y\),这些点和 \(x\) 构成了一个极大点双联通分量,注意不能把 \(x\) 弹出,因为一个割点可能属于多个点双联通分量。
边双联通分量("e-Dcc")的定义:这个图不存在割边。
边双联通分量的充要条件:任意两个点都存在至少两条边不重复的路径。
边双联通分量的性质:对于任意的 \(u,v,w\) 都存在一条从 \(u\) 到 \(v\) 的经过 边\(w\) 的简单路径。
边双联通分量缩点:把极大的边双联通分量缩成一个点,这要就可以把图变成树。
求法:求出所有割边后,把割边断掉,这样每个联通块就是一个极大边双联通分量。
圆方树
求出点双联通分量,称原图的点为圆点,然后对于每个极大点双联通分量建一个方点。
对于每个圆点,将其向所处的点双对应的方点连边。
这样会形成一棵树,其中如果只考虑割点和方点,那么每个方点会连接两个割点,然后对于非割点,他们会在对应方点上形成一个菊花。
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 200200;
int n, m;
struct Edge{
int nxt, to;
Edge() {}
Edge(int _nxt, int _to)
: nxt(_nxt), to(_to) {}
} e[N << 2];
int h[N], cnt;
void add(int u, int v) {
e[++cnt] = Edge(h[u], v), h[u] = cnt;
return;
}
int dfn[N], low[N], nw;
int st[N], top, cls;
vector<int> to[N];
bool cut[N];
void Add(int x, int y) {
to[x].push_back(y), to[y].push_back(x);
return;
}
int sn;
void Tarjan(int u, int rt) {
dfn[u] = low[u] = ++nw, st[++top] = u, ++sn;
int s = 0;
for (int i = h[u]; i; i = e[i].nxt) {
int v = e[i].to;
if (!dfn[v]) {
Tarjan(v, rt), low[u] = min(low[u], low[v]), ++s;
if (low[v] >= dfn[u]) {
if (u != rt || s > 1) cut[u] = 1;
cls++;
for (int x = 0; x != v; --top) {
x = st[top];
Add(cls + n, x);
}
Add(cls + n, u);
}
}
else low[u] = min(low[u], dfn[v]);
}
return;
}
signed main() {
scanf("%d%d", &n, &m);
for (int i = 1, u, v; i <= m; ++i) scanf("%d%d", &u, &v), add(u, v), add(v, u);
for (int i = 1; i <= n; ++i) {
if (!dfn[i])
sn = 0, Tarjan(i, i), --top;
}
return 0;
}
有向图强联通分量
强联通分量("Scc")的定义:对于任意两个点 \(u,v\),都存在一条从 \(u\) 到 \(v\) 的路径。
考虑在一个极大强联通分量中的点 \(u\),对于其他所有点 \(v\),由于存在 \(u\) 到 \(v\) 的路径,也存在 \(v\) 到 \(u\) 的路径,从而存在一个环包含 \(u\) 和 \(v\),所以只要找哪些点可以和 \(u\) 构成一个环。
同样还是对原图跑一个 dfs 生成树,假设 \(u\) 是其所在的极大强联通分量中最先访问的,那么其余的点一定在其子树中,这个是显然的。
考虑在 dfs 的过程中访问到了 \((u,v)\),若 \(v\) 没被访问过,则访问,然后用 \(low_v\) 更新 \(low_u\),否则若它不在别的 Scc 中,则用 \(dfn_v\) 更新 \(low_u\)。

浙公网安备 33010602011771号