• 图论·双连通分量


    注:双连通分量是针对无向图的概念。

    对于一个连通图,如果任意两点至少存在两条“点不重复”的路径,则说这个图是点-双连通的(双连通)。这个要求等价于任意两条边都在同一个简单环中,即内部无割顶。类似地,如果任意两点至少存在两条“边不重复”的路径,我们说这个图是边-双连通的,要求每条边都至少在一个简单环中,即所有边都不是桥。显然边双连通相对点双连通是一个更弱的条件。对于一张无向图,点-双连通的极大子图称为双连通分量(bcc)或块。不难看出,每条边恰好属于一个双连通分量,但不同双连通分量可能会有公共点。实际上,可以证明不同双连通分量最多只有一个公共点,且它一定是割顶同时,任意割顶都是至少两个不同双连通分量的公共点。

    首先了解一下无向图的割顶(cut vertex)与桥(bridge)。它们是保持连通分量连通性的关键结点或边,也就是说删除某个连通分量的割顶之后,原图不再连通,桥的判定方法类似。可以在线性时间内找到一张无向连通图的所有割顶。我们考虑对于一张无向连通图$G$进行dfs得到的dfs树,显然树根是割顶当且仅当它有两个或以上的子树,对于非树根结点有如下性质:

     在无向连通图$G$的dfs树中,非根结点$u$是$G$的割顶当且仅当$u$存在一个子结点$v$,使得以该子结点为根的子树中所有结点均没有反向边连回$u$的祖先。

     方便起见,设$low(u)$为$u$及其后代所能连回的最早的祖先的$pre$值,则上述条件就可以简单地写成结点$u$存在一个子结点$v$,使得$low(v) geq  pre(u)$。作为一种特殊情况,如果$v$的后代只能连回$v$自己(即$low(v) > pre(u) $),只需删除$(u, v)$一条边就可以让图$G$非连通,因此$(u, v)$是桥。下面给出判定图$G$中割顶的代码:

     1 int dfs(int u, int fa){
     2     int lowu = pre[u] = ++dfs_clk;
     3     int child = 0;
     4     FOR(i, 0, G[u].size() - 1){//FOR(i, j, k) :: for(int i = j; i <= k; i++)
     5         int v = G[u][i];
     6         if(!pre[v]){ // v never visited before
     7             child++;
     8             int lowv = dfs(v, u);
     9             minimize(lowu, lowv);//min(x, y) :: x = min(x, y)
    10             if(lowv >= pre[u]) is_cut[u] = 1; // u is vertex cut
    11         }else if(pre[v] < pre[u] && v != fa) minimize(lowu, pre[v]);
    12     }
    13     if(fa < 0 && child == 1) is_cut[u] = 0;
    14     low[u] = lowu;
    15     return lowu;
    16 }

     $pre(u)$是结点$u$在dfs时第一次被访问的时间,对于从结点$u$发出的边,如果指向一个未访问过的结点,可以用子结点的$low[v]$来更新$low[u]$,否则如果$v$不是$u$的父结点,说明该边是一条反向边($v$在dfs树中的父结点并不是$u$),用该边更新$low[u]$。

    计算点-双连通分量一般用如下算法(Tarjan):

     1 vector<int> G[maxn], bcc[maxn];
     2 int pre[maxn], is_cut[maxn], bcc_no[maxn];
     3 int dfs_clk, bcc_cnt;
     4 int odd[maxn], color[maxn];
     5 struct E{
     6     int u, v;
     7     E(int u = 0, int v = 0) : u(u), v(v) {}
     8 };
     9 stack<E> S;
    10 int dfs(int u, int fa){
    11     int lowu = pre[u] = ++dfs_clk;
    12     int ch = 0;
    13     FOR(i, 0, G[u].size() - 1){
    14         int v = G[u][i];
    15         E e = E(u, v);
    16         if(!pre[v]){
    17             S.push(e);
    18             ch++;
    19             int lowv = dfs(v, u);
    20             minimize(lowu, lowv);
    21             if(lowv >= pre[u]){
    22                 is_cut[u] = 1;
    23                 bcc_cnt++, bcc[bcc_cnt].clear();
    24                 while(true){
    25                     E x = S.top(); S.pop();
    26                     if(bcc_no[x.u] != bcc_cnt){
    27                         bcc[bcc_cnt].pb(x.u), bcc_no[x.u] = bcc_cnt;
    28                     }
    29                     if(bcc_no[x.v] != bcc_cnt){
    30                         bcc[bcc_cnt].pb(x.v), bcc_no[x.v] = bcc_cnt;
    31                     }
    32                     if(x.u == u && x.v == v) break;
    33                 }
    34             }
    35         }else if(pre[v] < pre[u] && v != fa){
    36                 S.push(e);
    37                 minimize(lowu, pre[v]);
    38         }
    39     }
    40     if(fa < 0 && ch == 1) is_cut[u] = 0;
    41     return lowu;
    42 }
    43 
    44 void find_bcc(int n){
    45     clr(pre, 0), clr(is_cut, 0), clr(bcc_no, 0);
    46     dfs_clk = bcc_cnt = 0;
    47     FOR(i, 0, n - 1) if(!pre[i]) dfs(i, -1);
    48     //post condition : S is empty
    49 }

     边-双连通分量可以用更简单的办法求出,分两个步骤,先做一次dfs标记出所有的桥,然后再做一次dfs找出边-双连通分量。因为边-双连通分量是没有公共结点的,所以只要在第二次dfs时不经过桥即可。

  • 相关阅读:
    最短路小变形
    最短路
    bfs + 路径输出
    优先队列
    dijkstra
    重载运算符-operator
    最短路
    二分图匹配
    依赖型的关系建立
    string 类
  • 原文地址:https://www.cnblogs.com/astoninfer/p/5757782.html
Copyright © 2020-2023  润新知