桥:如果删除某条边图不联通,称该边为桥。
割点:如果删除某点及其附属边后整个图不连通,成该点为割点。
两类双连通分量
边双连通分量(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(相当于叶节点),需要在该连通分量内部(非割点处)放一个出口