桥:如果删除某条边图不联通,称该边为桥。
割点:如果删除某点及其附属边后整个图不连通,成该点为割点。
两类双连通分量
边双连通分量(e_DCC):极大的不含桥的连通块。
删除边双连通分量中的任意边,整个图还是连通的
边双连通分量任意两点之间必有不少于两条不同路径
点双连通分量(v_DCC)/连通块:极大的不包含割点的连通块。
每一个割点至少属于两个点连通分量。
两个割点之间的边不一定是桥。例如一个H上边封闭的图。 桥两边的端点也不一定是割点。例如一条线段。
点连通分量和双连通分量也无必然关系 例如,一个8表示的图是边连通分量,不是点连通分量 一条线表示的图不是边连通分量,是点连通分量
对于一棵树来讲,每个节点都是一个边连通分量,每条边都是一个点连通分量
边连通分量
类似于强连通分量dfn, low数组
只存在前向边,树枝边,后向边,不存在横向边(因为边是没有方向的)
如何找到所有桥?
桥等价于对于一条树枝边x-y,dfn[x] < low[y]
如何找到所有边双连通分量?
方法1:删掉所有桥
方法2:类似于双连通分量的操作,使用栈辅助操作
AcWing 395. 冗余路径
边双连通分量等价于图上任意两点之间至少有两条完全不同的路径
证明:
充分性:反证法,假设图不是一个边双连通分量,说明存在桥,删除桥后,两侧节点之间没有可达路径,与已知矛盾,得证
必要性:归纳法
无向连通图,至少加多少条边能变成边双连通分量?
缩点后度为1的节点数(c + 1) // 2
连线方式:两个叶节点连起来与根节点成环。
import java.util.*;public class Main {static final int N = 5010, M = 20010;static int[] h = new int[N], e = new int[M], ne = new int[M];static int idx, p;static int[] dfn = new int[N], low = new int[N], stk = new int[N];static int dcc_cnt, time_cnt;static int n, m;static int[] id = new int[N];static boolean[] is_bridge = new boolean[M];static int[] degree = new int[N];public static void main(String[] args) {Scanner sc = new Scanner(System.in);n = sc.nextInt();m = sc.nextInt();Arrays.fill(h, -1);for (int i = 0; i < m; i++) {int a = sc.nextInt();int b = sc.nextInt();add(a, b);add(b, a);}tarjan(1, -1);for (int i = 0; i < idx; i++) {if (is_bridge[i]) {degree[id[e[i]]]++;}}int c = 0;for (int i = 1; i <= dcc_cnt; i++)if (degree[i] == 1)c++;System.out.println((c + 1) / 2);}static void tarjan(int u, int from) {dfn[u] = low[u] = ++time_cnt;stk[++p] = u;for (int i = h[u]; i != -1; i = ne[i]) {int j = e[i];if (dfn[j] == 0) {tarjan(j, i);low[u] = Math.min(low[u], low[j]);if (dfn[u] < low[j]) {is_bridge[i] = is_bridge[i ^ 1] = true;}} else if ((i ^ 1) != from) {low[u] = Math.min(low[u], dfn[j]);}}if (dfn[u] == low[u]) {int y;dcc_cnt++;do {y = stk[p--];id[y] = dcc_cnt;} while (y != u);}}static void add(int a, int b) {e[idx] = b;ne[idx] = h[a];h[a] = idx++;}}
点双连通分量
类似于强连通分量dfn, low数组
如何判断一个点是不是割点?
对于一条树枝边x-y,若low[y] >= dfn[x]
- 如果
x不是根节点,那么x是割点 x是根节点,至少有两个子节点yi,满足low[yi] >= dfn[x]
AcWing 1183. 电力
求每个割点最多能将图分成几个连通块
如何求点双连通分量?
// 求完tarjan(j)后if (dfn[u] <= low[j]) {cnt++;if (u != root || cnt > 1) // 说明x是割点{// 记录该点为割点}// 将栈中元素弹出,直至j被弹出为止// 将u加入该点双连通分量中}// 最后判断u是不是孤立点
AcWing 396. 矿场搭建
分别处理每个连通块
若连通块内无割点,任选两个点作为两个出口即可,cnt * (cnt - 1) / 2
若连通块内有割点,先缩点
- 每个割点作为单独一个点
- 从每个v-DCC向其所包含的每个割点连一条边,这样缩点后的图最多有2倍的原点数量
- v-DCC度为1(相当于叶节点),需要在该连通分量内部(非割点处)放一个出口
cnt - 1 - v-DCC度> 1,无需设置出口
- v-DCC度为1(相当于叶节点),需要在该连通分量内部(非割点处)放一个出口
