yl2026's blog

By yl2026, history, 4 months ago, In English

December 27, 2025 — Vertex Biconnectivity, Cut Vertices, and Bridges

Introduction

Good Bye 2025, here I come!!!

You have no egg!

Connected Components (Maximally Connected Subgraphs)

A subgraph is formed by selecting some vertices and edges from the original graph to create a new graph.
A connected subgraph must satisfy the condition that the entire subgraph is connected.
A maximally connected subgraph aims to include as many vertices and edges as possible while remaining connected. Note that it is "maximal," not "maximum."

Maximal: Adding any more vertices or edges would break the condition!

Cut Vertices:

In an undirected graph, if removing a vertex increases the number of maximally connected components, that vertex is called a cut vertex (or articulation point).

First, consider the following graph:

img

We can see that the cut vertex is 2, and this graph has only this one cut vertex.

First, assign timestamps (DFS order) to the vertices.

img

If for a vertex ( u ), there exists a child ( v ) that cannot reach any vertex with a timestamp earlier than ( u ), then ( u ) is a cut vertex.

This is because after removing ( u ), the subtree containing ( v ) has no back edges to reconnect.

img

But what if it is the root node?

If the root node has two or more children in the DFS tree, it is a cut vertex.
Otherwise, if it has only one child, it is not a cut vertex.

Code:

inline void tarjan(int u, int fa) {
    dfn[u] = low[u] = ++idx;
    bool flag = false;
    int cnt = 0;
    for (auto v : edges[u]) {
        if (!dfn[v]) {
            cnt++;
            tarjan(v, u);
            low[u] = min(low[u], low[v]);
            if (low[v] >= dfn[u] && u != fa && !flag) {
                flag = 1;
                c.push_back(u);
            }
        } else {
            low[u] = min(low[u], dfn[v]);
        }
    }
    if (!flag && u == fa && cnt > 1) {
        c.push_back(u);
    }
}

Bridges:

Similar to cut vertices, bridges are also called cut edges.

In an undirected graph, if removing an edge increases the number of connected components, that edge is called a bridge or cut edge.

For example, in the following graph:

Bridge Example

The red edges are bridges.

If there are no multiple edges between the same pair of vertices, simply change ( low_v \ge dfn_u ) to ( low_v > dfn_u ) in the cut vertex algorithm.
If there are multiple edges, they are definitely not bridges.

We can ignore the first edge.

Code:

inline void tarjan(int u, int fa) {
    dfn[u] = low[u] = ++idx;
    int mark = 0, flag = 0;
    for (auto [v, id] : edges[u]) {
        if (v == fa && !mark) {
            mark = 1;
        } else if (!dfn[v]) {
            tarjan(v, u);
            low[u] = min(low[u], low[v]);
            if (low[v] > dfn[u]) {
                flag = 1;
                ans.push_back(id);
            }
        } else {
            low[u] = min(low[u], dfn[v]);
        }
    }
}

Biconnected Components (Vertex Biconnected Components):

A biconnected component (vertex biconnected component) is a connected component that remains connected after removing any single vertex.

As shown in the figure:

bcc-counterexample.png

  • Two biconnected components share at most one common vertex, which is a cut vertex.
  • For a biconnected component, the vertex with the smallest ( dfn ) in the DFS tree must be a cut vertex or the root node.

Thus, we can use a stack to record visited vertices. During the backtracking phase after encountering a cut vertex, we can extract the biconnected components.

Note: Isolated vertices.

Code:

inline void tarjan(int u, int fa) {
    dfn[u] = low[u] = ++idx;
    stk[++top] = u;
    for (auto v : edges[u]) {
        if (v == fa) continue;
        if (!dfn[v]) {
            tarjan(v, u);
            low[u] = min(low[u], low[v]);
            if (low[v] >= dfn[u]) {
                vector<int> ve{u};
                cnt[u]++;
                while (ve.back() != v) {
                    ve.push_back(stk[top]);
                    cnt[stk[top]]++;
                    top--;
                }
                bcc.pb(ve);
            }
        } else {
            low[u] = min(low[u], dfn[v]);
        }
    }
    if (u == fa && cnt[u] == 0) {
        bcc.pb({u});
    }
}

Example Problem:

G — Sniffer

If ( u ) is a cut vertex and separates vertices ( A ) and ( B ) into two sides, then this vertex is valid.

If ( dfn_v \le dfn_B ), it is valid. We can then count the answers.

#include <bits/stdc++.h>

using namespace std;
#define ll long long
#define ull unsigned long long
#define db double
#define sz(x) ((int)x.size())
#define inf (1 << 30)
#define pb push_back
typedef pair<int, int> PII;
const int N = 5e5 + 7;
const int P = 998244353;
int read() {
    int x = 0, f = 1;
    char ch = getchar();
    while (!(ch >= '0' && ch <= '9')) {
        if (ch == '-') f = -f;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') {
        x = x * 10 + ch - '0';
        ch = getchar();
    }
    return x * f;
}
int n, m, a, b;
vector<int> edges[N], ans;
int dfn[N], low[N], idx;
inline void tarjan(int u, int fa) {
    dfn[u] = low[u] = ++idx;
    bool flag = false;
    int cnt = 0;
    for (auto v : edges[u]) {
        if (!dfn[v]) {
            tarjan(v, u);
            low[u] = min(low[u], low[v]);
            cnt++;
            if (low[v] >= dfn[u] && !flag && u != fa && dfn[b] >= dfn[v]) {
                flag = 1;
                ans.push_back(u);
            }
        } else {
            low[u] = min(low[u], dfn[v]);
        }
    }
    if (!flag && u == fa && cnt > 1) {
        ans.push_back(u);
    }
}
int fa[N];
int find(int x) {
    if (fa[x] == x) return x;
    return fa[x] = find(fa[x]);
}
void unite(int x, int y) {
    x = find(x), y = find(y);
    if (x != y) fa[x] = y;
}
void solve() {
    n = read();
    int x, y;
    for (int i = 1; i <= n; i++) fa[i] = i;
    while (scanf("%d%d", &x, &y)) {
        if (x == 0 && y == 0) break;
        m++;
        unite(x, y);
        edges[x].pb(y);
        edges[y].pb(x);
    }
    if (find(x) != find(y)) {
        puts("No solution");
        return;
    }
    a = read(), b = read();
    for (int i = 1; i <= n; i++) {
        if (!dfn[i]) {
            tarjan(i, i);
        }
    }
    if (ans.size() == 0) {
        puts("No solution");
        return;
    }
    sort(ans.begin(), ans.end());
    printf("%d\n", *ans.begin());
}
int main() {
    int oT_To = 1;
    while (oT_To--) solve();
    return 0;
}
  • Vote: I like it
  • +9
  • Vote: I do not like it