图论篇6——割点(关节点)

引入

连通图

    在一个**无向图**$G$中,若从顶点$i$ 到顶点$j$有路径相连,则称 $i$和$j$是连通的。如果图中任意两点都是连通的,那么图被称作连通图。如果$G$是有向图,则称为强连通图(注意:需要双向都有路径)。如果是单向连通,则称$G$为单向连通图。

割点(关节点)

    在无向连通图$G=(V,E)$中: 若对于$x\in V$, 从图中删去节点$x$以及所有与$x$关联的边之后, $G$分裂成两个或两个以上不相连的子图, 则称$x$为$G$的割点。 简而言之, 割点是无向连通图中的一个特殊的点, 删去中这个点后, 此图不再连通, 而所以满足这个条件的点所构成的集合即为割点集合。

割边(桥)

    如果删除$G$的一条边$b$,图$G$分离成两个非空子图,则称边$b$为图$G$的桥。如下图中,顶点$u$和$v$都是割点,其他顶点都不是割点,边$(u,v)$是桥,其他边都不是桥。

关节点识别

方案一:DFS (O(n^2))

依次去掉每一个点,判断图是否还连通。

方案二: Tarjan 算法

DFS树

首先需要了解一些关于深度优先搜索树(DFS tree)的概念。

以下图为例:

它的深度优先搜索树如下:

其中黑色的边为树边:如果结点\(u\)因算法对边\((u,v)\)的搜索而首次被发现,则\((u,v)\)是一条树边。

简单点说就是,它是正常的一颗树的边,只看\(1,2,3,4\)结点,是一颗树。【箭头只是表示搜索顺序】

其中红色的边为反向边:方向边\((u,v)\)是将结点\(u\)连接到其\(dfs\)树中的一个祖先结点\(v\)的边,环也被认为是反向边。

算法步骤

首先选定一个根节点,从该根节点开始遍历整个图(使用\(DFS\))。

对于根节点,判断是不是割点很简单,计算其子树数量就行,如果有2棵即以上的子树,就是割点。因为如果去掉这个点,这两棵子树就不能互相到达。

对于非根节点,判断是不是割点就有些麻烦了。我们维护两个数组\(dfn[]\)\(low[]\)

\(dfn[u]\):意思就是在\(dfs\)的过程中,当前的\(u\)结点是第几个(首次)被访问的。(之前一直不知道这个\(dfn\)是个什么缩写,豆腐脑?)

\(low[u]\):表示顶点\(u\)及其子树中的点,通过反向边,能够回溯到的最早的点(\(dfn\)最小)的\(dfn\)

对于边\((u, v)\),如果\(low[v]>=dfn[u]\),此时\(u\)就是割点。

算法可视为线性时间复杂度,采用邻接表存储的话,应与\(DFS\)相同,为\(O(V+E)\)

题目链接:https://www.luogu.org/problem/P3388

#include <iostream>
#include <set>
#include <stdio.h>
using namespace std;

int cnt, Time = 1;
int Low[20005], d[20005];
bool fuck[20005];
struct Node {
    int data;
    Node* next;
    Node() {}
    Node(int data) :data(data) {};
    void push(int to) {
        Node* s = new Node(to);
        s->next = next;
        next = s;
    }
}head[20005];

void DFS(int u, int father) {
    int cnt = 0;
    Low[u] = d[u] = Time++;
    Node* p = head[u].next;

    while (p) {
        int v = p->data;
        if (d[v] == -1) {//如果v尚未访问
            DFS(v, u);
            if (Low[v] < Low[u])Low[u] = Low[v];
            
            if (father != 0 && Low[v] >= d[u]) fuck[u] = true;
            
            if (father == 0)
                cnt++;    
        }
        //如果v已经访问,但不是v的双亲,则v是一条反向边
        Low[u] = Low[u] < d[v] ? Low[u] : d[v];
        p = p->next;
    }
    if (father == 0 && cnt >= 2)fuck[u] = true;
}

int main() {
    int i, n, m;
    cin >> n >> m;
    for (i = 1; i <= n; i++) {
        d[i] = -1;
        head[i].next = NULL;
    }

    for (i = 0; i < m; i++) {
        int c1, c2;
        //cin >> c1 >> c2;
        scanf("%d%d", &c1, &c2);
        head[c1].push(c2);
        head[c2].push(c1);
    }
    for (int i = 1; i <= n; i++) {
        if (d[i] == -1) DFS(i, 0);
    }
    //DFS(1, 0);

    int res = 0;
    for (int i = 1; i <= n; i++) {
        if (fuck[i])res++;
    }
    cout << res << endl;
    for (int i = 1; i <= n; i++) {
        if (fuck[i])cout << i << ' ';
    }
    cout << endl;
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/czc1999/p/11754046.html