史上代碼最簡單,講解最清晰的雙連通分量


 
 
 
史上代碼最簡單,講解最清晰的雙連通分量
(需提前學習強連通分量)
 
雙連通分量的主要內容包括割點、橋(割邊)、點雙和邊雙,分別對應 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);
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM