史上代码最简单,讲解最清晰的双连通分量

 
 
 
史上代码最简单,讲解最清晰的双连通分量
(需提前学习强连通分量)
 
双连通分量的主要内容包括割点、桥(割边)、点双和边双,分别对应 4 个 Tarjan 算法。
所有算法的时间复杂度均为 O(n + m)。
双连通分量用到 DFS 树的性质,所有的边分别树边和返祖边两类,大大简化了代码。
双连通分量具有大量的性质,要能熟练掌握。
一些定义:树枝边:DFS时经过的边(由上至下);
                 返祖边:与DFS方向相反,从某个节点指向某个祖先的边;
 
注意:在无向图中,不能用dfn[fa]更新low[u];所以我们需要标记fa;
           但如果有重边,就可以;所以我们可以记录它的上一条边;利用成对储存的思想记录上一条边来判重;
 
求割点:
    割点性质:
    (1)根结点如果是割点当且仅当其子节点数大于等于 2;
    (2)非根节点 u 如果是割点,当且仅当存在 u 的一个子树,子树中没有连向 u 的祖先的边(返祖边)。
    代码:
void tarjan(int u,int fa) //当fa=0时,说明该节点是根节点;
{
    int num=0; //用来计量子节点数;
    low[u]=dfn[u]=++cur;
    for(int i=head[u];i;i=star[i].to){ //链式前向星存图;
        int v=star[i].to;
        if(!dfn[v]){
            tarjan(v,u);
            low[u]=min(low[u],low[v]);
            if(!fa && ++num>1||fa && dfn[u]<=low[v]){  
            //1.根节点是割点,且子节点数大于等于2; 
            //2.非根节点是割点,且子节点中没有返祖边; 
                cutpoint[u]=1; //标记该点为一个割点;
            }
        }
        else if(v!=fa){
            low[u]=min(low[u],dfn[v]);
        }
    }
}

 

 求点双连通分量:

     以下 3 条等价(均可作为点双连通图的定义):
  (1)该连通图的任意两条边存在一个包含这两条边的简单环;
  (2)该连通图没有割点;
  (3)对于至少3个点的图,若任意两点有至少两条点不重复路径。

    下面两句话看不看的懂都行:

    点双连通分量构成对所有边集的一个划分。
    两个点双连通分量最多只有一个公共点,且必为割点。进一步地,所有点双与割点可抽象为一棵树结构。
#include <bits/stdc++.h>
using namespace std;
struct littlestar{
    int to;
    int nxt;
}star[200010];
int head[200010],cnt;
void add(int u,int v){
    star[++cnt].to=v;
    star[cnt].nxt=head[u];
    head[u]=cnt;
} 
int low[20010],dfn[20010],cur;
pair<int,int> st[200010];
int Top,num;
vector<int> res[20010];
void tarjan(int u,int fa)
{
    low[u]=dfn[u]=++cur; 
    for(int i=head[u];i;i=star[i].nxt){ //链式前向星存图 
        int v=star[i].to;
        int top=Top;
        if(v!=fa && dfn[u]>dfn[v]){
             st[++Top]=make_pair(u,v); //当这条边并不是通往父亲的边时,并且该点的子             
                                       //树中没有返祖边时,将这条边压入栈; 
        }
        if(!dfn[v]){
            tarjan(v,u);
            low[u]=min(low[u],low[v]);
            if(dfn[u]<=low[v]){
                ++num; //num表示第几个点双区域(一个图可能存在多个点双) 
                for(;Top>top;Top--){ //类似于强连通分量的退栈过程; 
                    int x=st[Top].first;
                    int y=st[Top].second;
                    if(res[x].empty() || res[x].back()!=num){
                        res[x].push_back(num); //由于num递增,所以res[]递增,所以res[x]的最后
                                               //如果不是num,就代表之前不会标记过该点; 
                    }
                    if(res[y].empty() || res[y].back()!=num){
                        res[y].push_back(num); //与上面的同理; 
                    }
                }
            }
        }
        else if(v!=fa){
            low[u]=min(low[u],dfn[v]);
        }
    }
}

 

求桥:

   桥的性质: (u; v)边在dfs 树中。不妨设u 为v 的父亲,v 的子树没有向u 或其祖先连的边。

 

void tarjan(int u,int fa)
{
    bool flag=0; //用来判断是否存在重边
    low[u]=dfn[u]=++cur;
    for(int i=head[u];i;i=star[i].nxt){
        int v=star[i].to;
        if(!dfn[v]){
            tarjan(v,u);
            low[u]=min(low[u],low[v]);
            if(dfn[v]==low[v]) //它的子节点v的子树中,没有像u或其祖先连的边(返祖边)
            {
                bridge.push_back(i);  //桥的一个集合
            }
        }
        else if(v!=fa || flag){
            low[u]=min(low[u],dfn[v]);            
        }
        else flag=1;
    }
}

 

 

 

 

求边双连通分量

        以下3 条等价(均可作为边双连通图的定义):

    (1)该连通图的任意一条边存在一个包含这条边的简单环;
    (2)该连通图没有桥;
    (3)该连通图任意两点有至少两条边不重复路径。

 

    下面两句话看不看的懂都行:

    (1)边双连通分量构成对所有点集的一个划分。
    (2)两个边双连通分量最多只有一条边,且必为桥。进一步地,所有边双与桥可抽象为一棵树结构。

#include <bits/stdc++.h>
using namespace std;
struct littlestar{
    int to;
    int nxt;
}star[10010];
int head[10010],cnt;
void add(int u,int v){
    star[++cnt].to=v;
    star[cnt].nxt=head[u];
    head[u]=cnt;
} 
int st[5010],Top,num;
int low[5010],dfn[5010],cur;
int res[5010];
int kk[150][150];
int anss[5001];
void tarjan(int u,int fa)
{
    bool flag=0;
    low[u]=dfn[u]=++cur;
    st[++Top]=u;
    for(int i=head[u];i;i=star[i].nxt){
        int v=star[i].to;
        if(!dfn[v]){
            tarjan(v,u);
            low[u]=min(low[u],low[v]);
        }
        else if(v!=fa || flag){
            low[u]=min(low[u],dfn[v]);
        }
        else flag=1;
    }  //到此为止与求桥的意义差不多
    if(low[u]==dfn[u]){ //u的子树中,没有返祖边
        num++;
        int tmp;
        do{
            tmp=st[Top--]; //退栈,原来栈中的元素构成一个边双
            res[tmp]=num;
        }while(tmp!=u);
    }
}

 

posted @ 2019-06-19 19:16  神之右大臣  阅读(2568)  评论(1编辑  收藏  举报