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

猜你喜欢

转载自www.cnblogs.com/xiongtao/p/9970426.html