POJ - 1144 (tarjan割點)
阿新 • • 發佈:2018-11-16
題目連結:http://poj.org/problem?id=1144
題目意思:求一個圖的割點個數
割點:在一個無向圖中,如果刪除一個頂點以及所有相關聯的邊以後,圖的連通分量增多,就稱這個點為割點。
首先選定一個根節點,從該根節點開始遍歷整個圖(使用DFS),樹上的邊一定都是圖上的邊,稱為樹邊,而圖上其餘沒遍歷的邊則為非樹邊(回邊)。
如果一個點不能通過非樹邊而回到比他樹上的父親的dfs序更小的點,那麼如果把它樹上的父結點刪掉,它就不能通過其他方法與圖的其他部分聯通,即其父節點為割點。其實對於根節點,如果它有不止一個的子樹,那它就是割點了。
voidView Codetarjan(int u) { low[u]=dfn[u]=++tot;//初始low[],dfn[] for(int i=head[u];i!=-1;i=edge[i].next)//遍歷每條相連的邊 { int v=edge[i].to;//與u相連的結點 if(!dfn[v])//如果v沒被遍歷過 { tarjan(v);//繼續搜尋v,找到low[v] low[u]=min(low[u],low[v]);//將u,v歸為一個子樹,所以取兩者最小 if(low[v]>=dfn[u])//判斷是否是割點 visit[u]++; } else//已經遍歷過 low[u]=min(low[u],dfn[v]); } }
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])
轉載自:https://www.cnblogs.com/collectionne/p/6847240.html
程式碼:
#include<iostream> #include<algorithm> #include<cstdio> #include<cstring> #define inf 0x3f3f3f3f using namespace std; typedef long long ll; const int maxn=150; struct node{ int next,to; }edge[maxn*maxn]; int head[maxn],visit[maxn],low[maxn],dfn[maxn]; int n,m,ans,cnt,tot; void init()//初始化 { memset(head,-1,sizeof(head)); memset(visit,0,sizeof(visit)); memset(low,0,sizeof(low)); memset(dfn,0,sizeof(dfn)); ans=cnt=tot=0; } void add(int u,int v)//連線兩點 { edge[cnt].to=v; edge[cnt].next=head[u]; head[u]=cnt++; } void tarjan(int u) { low[u]=dfn[u]=++tot;//初始low[],dfn[] for(int i=head[u];i!=-1;i=edge[i].next)//遍歷每條相連的邊 { int v=edge[i].to;//與u相連的結點 if(!dfn[v])//如果v沒被遍歷過 { tarjan(v);//繼續搜尋v,找到low[v] low[u]=min(low[u],low[v]);//將u,v歸為一個子樹,所以取兩者最小 if(low[v]>=dfn[u])//判斷是否是割點 visit[u]++; } else//已經遍歷過 low[u]=min(low[u],dfn[v]); } } int main() { while(~scanf("%d",&n)&&n) { init(); while(1) { int v; scanf("%d",&m); if(!m) break; while(1) { scanf("%d",&v); add(m,v); add(v,m); if(getchar()=='\n') break; } } tarjan(1); visit[1]--;//注意根結點1開始一定訪問了一次 for(int i=1;i<=n;i++) if(visit[i]>0) ans++; cout<<ans<<endl; } return 0; }