Featured image of post 割点、割边、点双连通分量、边双连通分量

割点、割边、点双连通分量、边双连通分量

Tarjan进阶应用

割点、割边、点双连通分量、边双连通分量

前置知识:

Tarjan算法

SCC缩点

割点

定义

在无向图中,若删除某个顶点 u 后,图的连通分量数量增加,则称 u 为割点

寻找方法

利用Tarjan的时间戳 dfn(记录节点首次被访问的顺序)和追溯值 low(记录节点或其子孙能通过非树边追溯到的最早时间戳)判断:

  • 对于根节点(DFS 树的起点):若其有至少 2 个子树,则为割点(删除根后子树间断开)。
  • 对于非根节点 u:若存在子节点 v 满足 $(low[v] \geq dfn[u])$,则 u 为割点。 (v 及其子孙无法绕过 u 到达更早访问的节点,删除 u 后 v 所在子树与其他部分断开)

视频讲解

实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
vector<int> g[N];
vector<int> ans;
int dfn[N],low[N],tstramp,root;
void tarjan(int x)
{
    dfn[x] = low[x] = ++tstramp;
    int child = 0;
    for(auto v : g[x])
    {
        if(!dfn[v])
        {
            tarjan(v);
            low[x] = min(low[x],low[v]);
            if(low[v]>=dfn[x])
            {
                child++;
                if(x!=root or child>1)//特判root节点
                    ans.push_back(x);
            }
        }
        else low[x] = min(low[x],dfn[v]);
    }
}

割边

定义

在无向图中,若删除某条边 $(u, v)$ 后,图的连通分量数量增加,则称该边为割边(或桥)

寻找方法

对于边 $(u, v)$(假设 u 是 v 在树中的父节点),若 $(low[v] > dfn[u])$,则 $(u, v)$ 是割边。 (v 及其子孙无法通过非树边追溯到 u 或更早节点,删除该边后 v 所在子树与其他部分断开)

与割点的区别

割边的判断更严格:$low[v] > dfn[u]$(等号不成立,若 $low[v] = dfn[u]$,说明存在其他路径连接 v 和 u,边非割边)

视频讲解

实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
vector<pair<int,int>> edges;
vector<int> idx[N];
vector<pair<int,int>> ans;
void add(int u,int v)
{
    edges.push_back({u,v});
    idx[u].push_back(edges.size()-1);
}

int dfn[N],low[N],tstramp;
void tarjan(int x,int ls)
{
    dfn[x] = low[x] = ++tstramp;
    for(auto t : idx[x])
    {
        int v = edges[t].second;
        if(!dfn[v])
        {
            tarjan(v,t);
            low[x] = min(low[x],low[v]);
            if(low[v] > dfn[x]) ans.push_back({x,v});
        }
        else if(t!=(ls^1))
            low[x] = min(low[x],dfn[v]);
    }
}

点双连通分量(vDCC缩点)

定义

  • 给定一个无向连通图 $G=(V,E)$。若去掉图中的任意 一个 顶点(及其所有关联边)都不会使图不连通,则称 $G$ 是 点双连通图

  • 点双连通分量是在图中最大的点双连通子图。换言之,它是一棵包含若干顶点和边的子图,对任一顶点的删除都不破坏其连通性,而且不能再向外扩展而保持此性质。

判定与求解

常用 Tarjan 来判定割点并划分

  • 在 DFS 过程中,为每个节点 $u$ 记录:

    • $dfn[u]$:访问次序编号。

    • $low[u]$:$u$ 或其任一后代能够追溯到的最小 $dfn$ 值。

  • 如果对于非根节点 $u$,存在子节点 $v$ 使得$ low[v] ≥ dfn[u]$,则 $u$ 是一个 割点,并且从 DFS 栈中弹出的所有边构成了一个 vDCC。

  • 对根节点特殊处理:如果根有至少两个子树,则它也是割点。

视频讲解

实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
int dfn[N],low[N],tstramp,cnt,root,idx[N];
bool cut[N];
stack<int> st;
vector<int> dcc[N];
vector<int> g[N];
void tarjan(int x)
{
    dfn[x] = low[x] = ++tstramp;
    st.push(x);
    if(!g[x].size())
    {
        dcc[++cnt].push_back(x);
        return;
    }
    int child = 0;
    for(auto v : g[x])
    {
        if(!dfn[v])
        {
            tarjan(v);
            low[x] = min(low[x],low[v]);
            if(low[v]>=dfn[x])
            {
                child++;
                if(x!=root or child>2)
                    cut[x] = true;
                cnt++;
                int y;
                do{
                    y = st.top();
                    dcc[cnt].push_back(y);
                    st.pop();
                }while(y != v);
                dcc[cnt].push_back(x);
            }
        }
        else low[x] = min(low[x],dfn[v]);
    }
}

void solve()
{
    int n,m;cin >> n >> m;
    for(int i=1;i<=m;i++)
    {
        int u,v;cin >> u >> v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    for(int i=1;i<=n;i++)
        if(!dfn[i]) root = i,tarjan(i);
    int num = cnt;
    //vDCC缩点
    for(int i=1;i<=n;i++)
        if(cut[i]) idx[i] = ++num;
    for (int i = 1; i <= cnt; i++) 
    {
        for (auto x : dcc[i]) 
        {
            if (cut[x]) 
            {
                ng[i].push_back(idx[x]),
                ng[idx[x]].push_back(i);
            }
            else idx[x] = i;
        }
    }
}

边双连通分量(eDCC缩点)

定义

  • 给定连通图 $G=(V,E)$。若去掉图中的任意 一条 边都不会使图不连通,则称 $G$ 是 边双连通图

  • 边双连通分量是图中最大的满足上述性质的子图。

判定与求解

边双连通分量的划分依赖于 (bridge)的发现:

  • 在 DFS 中同样维护 dfn[u]low[u]。若存在一条树边 $(u,v)$ 满足 low[v] > dfn[u],则该边是 ,去掉它会增加连通分支数。

  • 将图中所有桥删除后,剩余的连通块即为各个 eDCC。

视频讲解

实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
vector<pair<int,int>> edges;
vector<int> idx[N];
int dcc[N];        // 每个点所属的边双连通分量编号
bool cut[N * 2];   // 标记某条边是否是桥(true 表示桥)
int dfn[N], low[N], tstramp, cnt;
stack<int> st;

// 添加边 (无向图存两条边)
void add(int u,int v)
{
	edges.push_back({u,v});
	idx[u].push_back((int)edges.size()-1);
}

void tarjan(int x,int ls)
{
	dfn[x] = low[x] = ++tstramp;
	st.push(x);
	for(auto t : idx[x])
	{
		int v = edges[t].second;
		if(!dfn[v])
		{
			tarjan(v,t);
			low[x] = min(low[x],low[v]);
			if(low[v] > dfn[x]) cut[t] = cut[t ^ 1] = true;
		}
		else if(t != (ls ^ 1))
			low[x] = min(low[x], dfn[v]);
	}
	if(dfn[x] == low[x])
	{
		cnt++;
        int y;
		do{
			y = st.top(); 
			st.pop();
			dcc[y] = cnt;
		}while(y!=x)
	}
}

int in[N];
void solve()
{
	int n,m; cin >> n >> m;
	for(int i=1; i<=m; i++)
	{
		int u,v; cin >> u >> v;
		add(u,v); add(v,u);
	}
	for(int i=1; i<=n; i++)
		if(!dfn[i]) tarjan(i, 0);
	
	// 缩点后统计入度(示例)
	for(int i=0; i<(int)edges.size(); i++)
	{
		if(cut[i])
		{
			int u = edges[i].first;
			int v = edges[i].second;
			int uid = dcc[u];
			int vid = dcc[v];
			if(uid != vid)
				in[vid]++; // 桥连通不同的DCC,更新入度
		}
	}
}

示例

假设如下无向图:

1
2
3
4
5
6
7
    A
   / \
  B   C
   \ / \
    D   E
         \
          F
  • 割点:$D\text、C$

  • vDCC

    1. 子图 ${A, B, C, D}$(一个二连通块,有环 $A–B–D–C–A$)

    2. 子图 $(C, E)$(只有一条边,退化的二连通块)

    3. 子图 $(E, F)$

  • 割边: $(C,E)\text、(E,F)$

  • eDCC

    1. 主环 {A, B, C, D}

    2. 边 $(C,E)$ 单独一块

    3. 边 $(E,F)$ 单独一块

例题

P3388 【模板】割点(割顶)

P8435 【模板】点双连通分量

P8436 【模板】边双连通分量

P3854 [TJOI2008] 通讯网破坏

本作品采用知识共享署名-非商业性使用-相同方式共享4.0国际许可协议进行许可(CC BY-NC-SA 4.0)
文章浏览量:Loading
Powered By MC ZBD Studio
发表了43篇文章 · 总计69.19k字
载入天数...载入时分秒...
总浏览量Loading | 访客总数Loading

主题 StackJimmy 设计
由ZephyrBD修改