【Luogu P3388】割点模板

Luogu P3388

在一个无向图中,如果有一个顶点集合,删除这个顶点集合以及这个集合中所有顶点相关联的边以后,图的连通分量增多,就称这个点集为割点集合。
如果某个割点集合只含有一个顶点X(也即{X}是一个割点集合),那么X称为一个割点。

为了便于理解,我们可以从狭义上进行分析:对于一个连通无向图,删去其中的一个点可以令这个图不再连通,那么这个点就是割点(之一)。
那么再推广一下:对于一个无向图,删除其中一个点,使这个图的连通块增多,那么这个点就是割点之一。

对于这一类题目,仍然可以采用类似求强连通分量时使用的Tarjan算法进行求解。
【Luogu P3387】缩点模板(强连通分量Tarjan&拓扑排序)
如果没有学习过相关知识,可以看这一篇博客。

做法如下:
先考虑对于当前搜索树的根节点,很容易想到如果它有多棵子树(其实这么说不太对,可以说是根节点有多个直接子节点),那么这个根节点肯定就会是一个割点。
那么对于非根节点呢?像是求强连通分量时的做法一样,寻找返祖边。用low数组记录每个点能返回的最早访问的节点。
想象下面这样一种情况:如果low[v]>=dfn[u],说明v节点及其以下的任何一个节点都不可能不经过u节点访问到u节点以前的节点,在这样的情况下,u节点就成为了一个割点。
还有一个通常争议较大的地方,比较难以理解:
假设当前节点走到了一条往回走的边
low[now]=min(low[now],dfn[v]);//now即上文提到的u
在更新low的时候,这一句不能够写成下面的形式
low[now]=min(low[now],low[v]);
原因如下:我们知道low[v]记录是v能通过非树边(返祖边,横叉边)访问到的最早的节点,那么low[v]记录的节点必然在v之前或刚好为v。假设这样一种情况,low[v]记录的节点在v之前,那么按照第二种更新方法,low[now]被更新成v以前的节点。当我们回溯到节点v进行判断时,我们不会将v节点判定为割点,因为他的子节点now能访问到v以前的节点,不符合low[v]>=dfn[u]的条件。事实上,这种情况下,v也是一个割点。
可以自行画图进行理解
代码如下:

#include<iostream>
#include<cstdio>
using namespace std;
const int maxn=2*1e4+15,maxm=1e5+10;
struct data
{
    int to,nxt;
}e[2*maxm];
int dfn[maxn],low[maxn],tim,head[maxn],n,m,x,y,ans;
bool cut[maxn];
void tarjan(int now,int root)
{
    int bt=0;
    dfn[now]=low[now]=++tim;
    for (int i=head[now];i;i=e[i].nxt)
    {
        int v=e[i].to;
        if (!dfn[v])
        {
            tarjan(v,root);
            low[now]=min(low[now],low[v]);
            if (low[v]>=dfn[now]&&now!=root) cut[now]=true;
            if (now==root) bt++;
        }
        low[now]=min(low[now],dfn[v]);
    }
    if (now==root&&bt>1) cut[root]=true;
}
int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;i++) 
    {
        scanf("%d%d",&x,&y);
        e[i].to=y;e[i+m].to=x;
        e[i].nxt=head[x];e[i+m].nxt=head[y];
        head[x]=i;head[y]=i+m;
    }
    for (int i=1;i<=n;i++) if (!dfn[i]) tarjan(i,i);
    for (int i=1;i<=n;i++) if (cut[i]) ans++;
    printf("%d\n",ans);
    for (int i=1;i<=n;i++) if (cut[i]) printf("%d ",i);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/notscience/p/11824276.html