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

猜你喜欢

转载自blog.csdn.net/weixin_42557561/article/details/81634199
今日推荐