zoukankan      html  css  js  c++  java
  • 割点

    割点的概念

    无向连通图中,如果将其中一个点以及所有连接该点的边去掉,图就不再连通,那么这个点就叫做割点(cut vertex / articulation point)。

    例如,在下图中,0、3是割点,因为将0和3中任意一个去掉之后,图就不再连通。如果去掉0,则图被分成1、2和3、4两个连通分量;如果去掉3,则图被分成0、1、2和4两个连通分量。

    Tarjan算法

    可以使用Tarjan算法求割点(注意,还有一个求连通分量的算法也叫Tarjan算法,与此算法类似)。(Tarjan,全名Robert Tarjan,美国计算机科学家。)

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

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

    对于非根节点,判断是不是割点就有些麻烦了。我们维护两个数组dfn[]和low[],dfn[u]表示顶点u第几个被(首次)访问,low[u]表示顶点u及其子树中的点,通过非父子边(回边),能够回溯到的最早的点(dfn最小)的dfn值(但不能通过连接u与其父节点的边)。对于边(u, v),如果low[v]>=dfn[u],此时u就是割点。

    但这里也出现一个问题:怎么计算low[u]。

    假设当前顶点为u,则默认low[u]=dfn[u],即最早只能回溯到自身。

    有一条边(u, v),如果v未访问过,继续DFS,DFS完之后,low[u]=min(low[u], low[v]);

    如果v访问过(且u不是v的父亲),就不需要继续DFS了,一定有dfn[v]<dfn[u],low[u]=min(low[u], dfn[v])。

    Tarjan算法

    首先假设u是根节点。如果u有两棵以上的子树,则u为割点。代码:

    int children = 0;
    for (int v: g[u])
    {
        if (!vis[v])
        {
            children++;
            dfs(v);   // 继续DFS
        }
    }
    if (children >= 2)
        // u是割点

    非根节点呢?按照前面的描述,代码如下:

     1 // 默认u不能回溯到任何前面的点
     2 low[u] = dfn[u];
     3 for (int v: g[u])
     4 {
     5     // (u, v)为树边
     6     if (!vis[v])
     7     {
     8         // 设置v的父亲为u
     9         parent[v] = u;
    10         // 继续DFS,遍历u的子树
    11         dfs(v);
    12         // u子树遍历完毕,low[v]已求出,low[u]取最小值
    13         low[u] = min(low[u], low[v]);
    14 
    15         if (low[v] >= dfn[u])
    16             // u是割点
    17     }
    18     // (u, v)为回边,且v不是u的父亲
    19     else if (v != parent[u])
    20         low[u] = min(low[u], dfn[v]);
    21 }

    综合起来,加上一些其它部分,Tarjan算法的代码如下:

     1 const int V = 20;
     2 int dfn[V], low[V], parent[V];
     3 bool vis[V], ap[V];
     4 vector<int> g[V];
     5 
     6 void dfs(int u)
     7 {
     8     static int count = 0;
     9     // 子树数量
    10     int children = 0;
    11 
    12     // 默认low[u]等于dfn[u]
    13     dfn[u] = low[u] = ++count;
    14     vis[u] = true;
    15 
    16     // 遍历与u相邻的所有顶点
    17     for (int v: g[u])
    18     {
    19         // (u, v)为树边
    20         if (!vis[v])
    21         {
    22             // 递增子树数量
    23             children++;
    24             // 设置v的父亲为u
    25             parent[v] = u;
    26             // 继续DFS
    27             dfs(v);
    28             // DFS完毕,low[v]已求出,如果low[v]<low[u]则更新low[u]
    29             low[u] = min(low[u], low[v]);
    30 
    31             // 如果是根节点且有两棵以上的子树则是割点
    32             if (parent[u] == -1 && children >= 2)
    33                 cout << "Articulation point: " << u << endl;
    34             // 如果不是根节点且low[v]>=dfn[u]则是割点
    35             else if (parent[u] != -1 && low[v] >= dfn[u])
    36                 cout << "Articulation point: " << u << endl;
    37         }
    38         // (u, v)为回边,且v不是u的父亲
    39         else if (v != parent[u])
    40             low[u] = min(low[u], dfn[v]);
    41     }
    42 }

    不过有一个问题:可能会重复输出一个割点。例如一个图里有(1, 2)、(1, 3)、(1, 4)和(1, 5)四条边(取1为根节点),发现(1, 3)时就已经输出了1,但发现(1, 4)和(1, 5)时就又输出了两遍。所以需要使用一个数组ap[]来记录割点。

    还有一个可以优化的地方:我们使用vis[]来记录一个点是否访问过。但是我们想一下,不是只有访问过的点才会分配dfn吗?当然,没有访问过的顶点,dfn[]里也有值,但这里dfn[]是全局的,因此它的每个元素最初都是0。因此完全可以取消vis[]数组并把!vis[v]改成!dfn[v]。

    最后一个点:下面的代码:

    if (parent[u] == -1 && children >= 2)
        cout << "Articulation point: " << u << endl;
    else if (parent[u] != -1 && low[v] >= dfn[u])
        cout << "Articulation point: " << u << endl;

    可以合起来写成:

    if (parent[u] == -1 && children >= 2 || parent[u] != -1 && low[v] >= dfn[u])
        cout << "Articulation point: " << u << endl;

    对Tarjan算法的详细理解

    首先,“根节点有n棵子树”这句话,是说这n棵子树是独立的,没有根节点不能互相到达。因此n不一定等于与根节点相邻的顶点数。因此加入了vis[v]为false的条件,因为如果(u, v1)和(u, v2)在一棵子树里,对v1进行DFS,一定能去到v2,vis[v2]就会为true,此时就不会children++了。

    对于边(u, v),如果low[v]>=dfn[u],即v即其子树能够(通过非父子边)回溯到的最早的点,最早也只能是u,要到u前面就需要u的回边或u的父子边。也就是说这时如果把u去掉,u的回边和父子边都会消失,那么v最早能够回溯到的最早的点,已经到了u后面,无法到达u前面的顶点了,此时u就是割点。

  • 相关阅读:
    DC中为什么要用Uniquify?
    hdu 1596 find the safest road
    hdu2112 HDU Today
    hdu 2066 一个人的旅行
    poj 3026 Borg Maze
    poj 1979 Red and Black
    poj 1321 棋盘问题
    hdu 1010 Tempter of the Bone
    hdu 4861 Couple doubi
    codeforces584B Kolya and Tanya
  • 原文地址:https://www.cnblogs.com/Miroerwf/p/7784658.html
Copyright © 2011-2022 走看看