今天我们接着搞图论:割点和割边

(一)割点

啥叫割点?

针对无向连通图,若删除一个点后使得该图不连通,则该点是割点。

注意:一个图中可能有多个割点

 

先上一组数据:

 6 7

1 4
1 3
4 2
3 2
2 5
2 6

5 6

图是这样的:

 

很容易看出结果是:

2

 

那么如何求出图中的割点呢?

Algorithm1:dfs或bfs暴搜,不推荐也不想讲

Algorithm2:

我们可以从任意一个顶点开始遍历,用一个num数组来储存每个顶点是第几个访问到的。(有个专业术语叫时间戳)

上面一组数据的num是这样的:

    1 2 3 4 5 6
num 1 3 2 4 5 6

我们在遍历所有点时会遇到割点(废话),主要是如何认定一个点是割点。假设访问到了k点,如果在没有访问过的点中,至少有一个点在不经过k点的情况下,无法回到已访问过的点,则k点是割点。(因为该图删除点k后不连通了)

算法核心:如何判断未被访问过的点u在不经过点k的情况下能否返回任何一个已访问过的点。

从树的角度来看,k是u的父亲,u是k的儿子,判断u能否不经过k而回到它的所有祖先。

我们用数组low来表示每个点在不经过父节点的前提下,能返回的最早的时间戳。

上面一组数据的low是这样的:

    1 2 3 4 5 6
low 1 1 1 1 3 3

首先枚举k,再枚举跟k有边相连的u,如果存在low[u]>=num[k],即返回祖先必须经过k,则k是割点。

整个过程可以用dfs来实现。

下面呈上代码:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
int n,m,e[1005][1005],root,num[1005],low[1005],flag[1005],index;

void dfs(int cur,int dad)
{
    int i,child=0;
    index++;
    num[cur]=index;
    low[cur]=index;
    for(i=1;i<=n;i++)
    {
        if(e[cur][i]==1)
        {
            if(num[i]==0)
            {
                child++;
                dfs(i,cur);
                low[cur]=min(low[cur],low[i]);
                if(cur!=root && low[i]>=num[cur])
                {
                    flag[cur]=1;
                }
                else if(cur==root && child==2)
                {
                    flag[cur]=1;
                }
            }
            else if(low[i]!=dad)
            {
                low[cur]=min(low[cur],num[i]);
            }
        }
    }
}
int main()
{
    int i,a,b,c;
    scanf("%d%d",&n,&m);
    memset(e,0,sizeof(e));
    for(i=1;i<=m;i++)
    {
        scanf("%d%d",&a,&b);
        e[a][b]=1;
        e[b][a]=1;
    }
    root=1;
    dfs(1,root);
    return 0;
}
假装是割点

相关文章: