题解 P3388 【【模板】割点(割顶)】

· · 题解

简介一下割点,给自己复习一下,也是给像我一样的蒟蒻介绍一下割点

首先,什么是割点?

在浏览其他题解之后我知道割点

定义: 去掉无向联通图的某个点后,此图不连通,该点为割点

然后就是怎么求:

如图,我们将一张图抽象如上,1表示图的上部,5表示下部,中间由环相连。

我们用dfn[x]表示x被访问到的时间,low[x]表示它所在的那一坨的最小值 (感性理解一下)

我们从点1开始dfs,记录每一个点的时间戳,然后去搜所有没有搜过的,如果,他的儿子的最上面的时间戳(low[v])

low[v]>=dfn[x]说明v最早也是在x及之后了,也就意味着x上面的与v只能通过x相连,也就是说,只要x上面有(x不为本组祖先),那么x就是割点

如果x是祖先呢?

那我们将x找到一个儿子便让x的儿子++,并让儿子搜一遍(保证和此儿子联通的都被搜过),那么下次找到的儿子就是和此儿子不连通的,那么只要把x去掉,他两个儿子就不连通了,所以,x为割点

不理解上面的算法结合图看,把①去掉,2,3这两个儿子就不连通了

还有一个问题:大佬都说这里low要这样写:low[x]=min(low[x],dfn[v])

至于为什么不像强联通分量里一样,写low[x]=min(low[x],low[v])呢?

因为强联通里只要在强联通中,怎么连边都可以(我们又不会在强联通中割点),但割点不行,我们不能随便连边,low[x]=min(low[x],low[v])表示所有v都连与最先祖先的上,如下图1,但很明显,3,4,5并没有这一条路,所以如果你这么写,代码会认为有这一条路,从而出错

当然,由图的关系得到的low并不会有假边所以写low[x]=min(low[x],low[v])(不理解看代码)

实际:

写low[x]=min(low[x],low[v]):

代码:

void tag(int x,int zx)
{
    int kid=0;
    dfn[x]=low[x]=++tim;
    for(int i=head[x];i;i=p[i].next)
    {
        int v=p[i].to;
        if(!dfn[v])
        {
            tag(v,zx);
            low[x]=min(low[v],low[x]); //是有真边构成,不用改
            if(low[v]>=dfn[x]&&x!=zx) cut[x]=1;//后面无法通过别的方法连接到前面,是割点
            if(x==zx) kid++;
        }
        low[x]=min(low[x],dfn[v]); //要改的指这个
    }
    if(kid>1&&x==zx) cut[x]=1; //祖先有两个+的孩子,是割点
}

完整ac代码:(高清无码

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5+5;
int n,m;
struct edge
{
    int next,to;
}p[N];
int head[N],num;
void ad(int x,int y)
{
    p[++num].next=head[x];
    p[num].to=y;
    head[x]=num;
}
int dfn[N],low[N],tim,cut[N];
void tag(int x,int zx)
{
    int kid=0;
    dfn[x]=low[x]=++tim;
    for(int i=head[x];i;i=p[i].next)
    {
        int v=p[i].to;
        if(!dfn[v])
        {
            tag(v,zx);
            low[x]=min(low[v],low[x]);
            if(low[v]>=dfn[x]&&x!=zx) cut[x]=1;
            if(x==zx) kid++;
        }
        low[x]=min(low[x],dfn[v]);
    }
    if(kid>1&&x==zx) cut[x]=1;
}
int ans;
int main()
{
    cin>>n>>m;
    for(int i=1;i<=m;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        ad(x,y);
        ad(y,x);
    }
    for(int i=1;i<=n;i++) if(!dfn[i]) tag(i,i);
    for(int i=1;i<=n;i++) ans+=cut[i];
    printf("%d\n",ans);
    for(int i=1;i<=n;i++)
        if(cut[i]) printf("%d ",i);
    return 0;
}

2020.1.19

更新:

今天又写了一下发现了一个问题:

正确

    for(int i=head[u];i;i=p[i].next)
    {
        int v=p[i].to;
        if(!dfn[v])
        {
            taj(v,zx);
            low[u]=min(low[u],low[v]);
            if(u==zx) kid++;
            if(low[v]>=dfn[u]&&u!=zx) cut[u]=1;
        }
        else if(vis[v]) low[u]=min(low[u],dfn[v]);

    }

错误

    for(int i=head[u];i;i=p[i].next)
    {
        int v=p[i].to;
        if(!dfn[v])
        {
            taj(v,zx);
            low[u]=min(low[u],low[v]);
            if(u==zx) kid++;

        }
        else if(vis[v]) low[u]=min(low[u],dfn[v]);
        if(low[v]>=dfn[u]&&u!=zx) cut[u]=1;
    }

就是这个判断的时候,一定是要

只有直接儿子才可以按if(low[v]>=dfn[u]&&u!=zx) cut[u]=1;判,毕竟人家就是这么走的,但是如果是间接儿子,那么,实际上它应该是从别处走来的,所以这么写是错的