割点

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\n",cntt);
    for(int i=1;i<=n;i++)
        if(ans[i]) printf("%d ",i);
    return 0;
}


2018.6.7

posted @ 2018-06-07 15:25  露迭月  阅读(258)  评论(1编辑  收藏  举报