1. 程式人生 > >POJ - 1144 (tarjan割點)

POJ - 1144 (tarjan割點)

題目連結:http://poj.org/problem?id=1144

題目意思:求一個圖的割點個數

割點:在一個無向圖中,如果刪除一個頂點以及所有相關聯的邊以後,圖的連通分量增多,就稱這個點為割點。

 

首先選定一個根節點,從該根節點開始遍歷整個圖(使用DFS),樹上的邊一定都是圖上的邊,稱為樹邊,而圖上其餘沒遍歷的邊則為非樹邊(回邊)。

如果一個點不能通過非樹邊而回到比他樹上的父親的dfs序更小的點,那麼如果把它樹上的父結點刪掉,它就不能通過其他方法與圖的其他部分聯通,即其父節點為割點。其實對於根節點,如果它有不止一個的子樹,那它就是割點了。

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]); } }
View Code

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;
}