0813-割點詳講

今天這個割點快要把我搞死了,不過最後發現還是很簡單的,所以爲了防止我以後忘記,我打算好好的儘可能詳盡的寫一篇博客

在開始今天的講解之前,你需要有一些Tarjan算法的基礎

割點定義

在一個無向連通圖中,如果將其中一個點以及所有連接該點的邊去掉,圖不再連通,那麼這個點就叫做割點(cut vertex / articulation point)。

例如:在下圖中,0、3是割點,因爲將0和3中任意一個去掉之後,圖就不再連通。如果去掉0,則圖被分成1、2和3、4兩個連通分量;如果去掉3,則圖被分成0、1、2和4兩個連通分量。

 

 

 

割點的性質

若該割點爲根節點,則其子樹個數大於1

若該割點爲非根節點,則 dfn [ u ] <= low [ v ] (其中 v 爲該割點的兒子節點,這兩個變量均來源於Tarjan算法) 

 

怎麼求割點

明確了割點的兩個性質後就很方便求解啦,我們運用 tarjan 的 dfs 版算法即可

首先選定一個根節點,從該根節點開始遍歷整個圖(使用DFS)。

對於根節點,我們計算其子樹數量,如果有2棵即以上的子樹,就是割點。(因爲如果去掉這個點,這兩棵子樹就不能互相到達--割點性質1)

對於非根節點我們維護兩個數組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未訪問過(也就是dfn[ v ] == 0),繼續DFS,DFS完之後,low[u]=min(low[u], low[v]);

如果v訪問過(dfn [ v ] 有值),就不需要繼續DFS了,一定有dfn[v]<dfn[u],low[u]=min(low[u], dfn[v])。

那麼求割點的算法大致就是這樣了

還有一些細節的小問題(把我卡死了的),在這裏提一下 

  1. 怎麼求根節點的子樹個數:由於我們是 dfs 的,所以會一條鏈似的走下去,而對於根節點而言,每走到一個 dfn [ y ] == 0 的時候就意味着多了一棵子樹
  2. 爲什麼不能用累計的方法(就是 gedot [ ++num ] = x)統計割點,而是要用打標記的方法(就是 gedot [ x ] = 1):因爲會重複計算,舉個例子,想象一個菊花圖(就是每個點都只與1號節點有一條邊相連),然後模擬一遍就會發現1號節點會被重複計算 n - 2次

代碼

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
#define N 200009
using namespace std;
int n,m,root;
int nxt[N],head[N],to[N],tot,cnt=0;
int dfn[N],low[N],gedot[N];
void add(int x,int y){	nxt[++tot]=head[x];head[x]=tot;to[tot]=y;}
void tarjan(int x){
    dfn[x]=low[x]=++cnt;
    int i,j,k,child=0;
    for(i=head[x];i;i=nxt[i]){
        int y=to[i];
        if(dfn[y]==0){
            child++;
            tarjan(y);
            if(low[y]<low[x]) low[x]=low[y];
            if(low[y]>=dfn[x]&&x!=root)   gedot[x]=1;
            if(x==root&&child>1) gedot[x]=1;
        } 
        else low[x]=min(low[x],dfn[y]);
    }
}
int main(){
    scanf("%d%d",&n,&m);
    int i,j,k;
    for(i=1;i<=m;++i){
        scanf("%d%d",&j,&k);
        add(j,k);add(k,j);
    }
    for(i=1;i<=n;++i)
        if(!dfn[i]) root=i,tarjan(i);
   for(i=1;i<=n;++i)
    	if(gedot[i]) num++;
    printf("%d\n",num);
    for(i=1;i<=n;++i)
        if(gedot[i]) printf("%d ",i);
    return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章