割点

P3388 【模板】割点(割顶)

题目背景

割点

题目描述

给出一个(n)个点,(m)条边的无向图,求图的割点。

输入输出格式

输入格式:

第一行输入(n,m)

下面(m)行每行输入(x,y)表示(x)(y)有一条边

输出格式:

第一行输出割点个数

第二行按照节点编号从小到大输出节点,用空格隔开

说明

(n,m)均为100000

(tarjan)图不一定联通!!!


割点:在一个无向连通图中,如果有一个顶点集合,删除这个顶点集合以及这个集合中所有顶点相关联的边以后,原图变成多个连通块,就称这个点集为割点集合。

以上是广义一点的定义,对这个题,要求的为求解所有联通子块的割点,且这个割点集合仅有一个元素。

首先我们明确割点的几个性质:(在一个连通图中)

  1. 若点(u)是一个割点,则至少存在一对点(v_1,v_2),使得(v_1,v_2)的任何一个路径通过点(u)
  2. 对于一颗树,若某个点的入度大于1,则这个点为割点。

有了以上两个性质,我们可以借助(tarjan)求强连通分量的思想求解割点了。

首先,我们同样定义(dfn)(low)数组,分别代表 遍历到某点的时间 和 某点不通过它父亲下能到达的最小时间

现在开始利用性质1,对于图中某父节点(u)(u)不为遍历开始的点)和其子节点(v),在刚进入子节点时,父亲的(dfn)是一定大于子节点的,我们遍历子节点不通过(u)能够到达的边,并用最小值更新(low)数组。遍历结束后,若(low[v]>=dfn[u])即点(v)不通过连接它父亲的边是不能到达它父亲上面的点的(即时间戳小于(dfn[u])的点),那么点(u)即满足了性质1,成为了割点。

对于父节点(u)为遍历使点的时候,我们将连通图(G)看做是一颗树(当存在(u)为某环上的点时,等效为断开与(u)相连的某一条边,因为我们此时本来就是在探讨(u)是否为割点,所以断开其实不影响判断),此时,若这个(u)的入(出)度大于1,则(u)为割点。(其实这个模拟一下也是可以想明白的)

最后说一个问题:在某点更新(low)时,若用来更新的点不能进去(即已经进去),一定要写成(low[now]=min(low[now],dfn[v])),而不是(low[now]=min(low[now],low[v]);)
为什么?考虑这样一种情况,若点(i)恰好可以到达自己的父亲(u)(不通过父亲找到它的那条边),按照性质1,它父亲(u)仍然是割点。但是,如何它父亲(u)先经过某条边把自己的(low)更新小了,岂不是就错了吗,可以看看这个例子模拟理解一下。

按1,2,3,3,4,5顺序遍历即可


code:

#include <cstdio>
int min(int x,int y){return x<y?x:y;}
const int N=100010;
struct Edge
{
    int to,next;
}edge[N<<1];
int head[N],cnt=0,n,m;
void add(int u,int v)
{
    edge[++cnt].next=head[u];edge[cnt].to=v;head[u]=cnt;
}
int time=0,dfn[N],low[N],ans[N];
void tarjan(int now,int fa)
{
    int child=0;
    dfn[now]=low[now]=++time;
    for(int i=head[now];i;i=edge[i].next)
    {
        int v=edge[i].to;
        if(!dfn[v])
        {
            tarjan(v,fa);
            if(low[v]>=dfn[now]&&now!=fa)
                ans[now]=1;
            if(now==fa)
                child++;
            low[now]=min(low[now],low[v]);
        }
        low[now]=min(low[now],dfn[v]);
    }
    if(now==fa&&child>1) ans[now]=1;
}
int main()
{
    scanf("%d%d",&n,&m);
    int u,v;
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&u,&v);
        add(u,v),add(v,u);
    }
    for(int i=1;i<=n;i++)
        if(!dfn[i])
            tarjan(i,i);
    int cntt=0;
    for(int i=1;i<=n;i++)
        if(ans[i]) cntt++;
    printf("%d
",cntt);
    for(int i=1;i<=n;i++)
        if(ans[i]) printf("%d ",i);
    return 0;
}


2018.6.7

原文地址:https://www.cnblogs.com/butterflydew/p/9150805.html