强连通割点

定义

割点定义:如果去掉了 $i$ 号点和关于 $i$ 的所有的边,包含i的那个连通块(也就是联通分量)如果被分成了两个连通块,那么 $i$ 就是割点。

思路

  1. 输入

  2. $dfs$ 没有遍历过的点

    我们在 $dfs$时记录两个变量:表示遍历序和遍历序最小的祖先的遍历序。我们在搜索时可以直接判断,要是一个点连向的另外一个点和已遍历过的所有点如果都不联通,那就证明当前搜索到的这个点是割点。

  3. 计数和输出

    对于割点数目计数,逐一输出割点编号

代码

#include<bits/stdc++.h>
using namespace std;
const int N=200010;
int a[N],nxt[N],head[N];
int dfn[N],low[N],cnt,k;//low数组表示能会到的最早遍历到的祖先点,也就是祖先里最小的dfn值;cnt用来记录遍历顺序;k为建边数组的长度
bool cut[N],bst[N];//cut数组来记录第i个点是不是割点
void add(int x,int y){//用邻接表(可以叫链表或链式前向星)来存储边,因为数据范围比较大
    a[++k]=y;//连向的点
    nxt[k]=head[x];//记录下一条边(起点为x的边)
    head[x]=k;//记录同一个起点最早出现的边的编号
}
void tarjan(int u,int mr){
    int rc=0;
    dfn[u]=low[u]=++cnt;//记录初始的遍历顺序和遍历序号最小的祖先
    for(int i=head[u];i;i=nxt[i]){
        int v=a[i];//这条边连向的终点
        if(!dfn[v]){//如果没有遍历过,因为是深搜,如果不加这个条件它就会一直搜下去
            tarjan(v,mr);//递归,继续对与v点进行深搜
            low[u]=min(low[u],low[v]);//搜完时(及更新完v点能到达的最早遍历的祖先时)再更新最早能到达的祖先(毕竟u,v是联通的,v能到达的u一定能到达)
            if(low[v]>=dfn[u]&&u!=mr){//如果v最早能到达的点的编号大于等于u的遍历编号,证明它到不了之前搜过的点,证明v如果不经过u的话和之前到达的点是不联通的,也就是相当于只要没了u,就会被割成两部分
                cut[u]=1;//标记u是割点
            }
            if(u==mr){//如果这个点是通过起点扩展出去的
                rc++;//起点扩展出去的点数加1
            }
        }
        low[u]=min(low[u],dfn[v]);//取能到达的点的遍历序号的最小值
    }    
    if(rc>=2){//如果起点扩展出去的点数大于等于2,也就证明先从起点搜出了能到达的第1部分,但是还有点是从起点连出的并且没有和第1部分相连(相连的话是能直接搜到标记掉的,但这里没标记)
        cut[mr]=1;//标记这个起点为割点
    }
}
int main(){
    int n,m,ans=0;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        int x,y;
        scanf("%d%d",&x,&y);
        add(x,y);//无向图的双向边之一
        add(y,x);//无向图的双向边之一
    }
    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;
}
posted @ 2023-01-06 15:34  徐子洋  阅读(10)  评论(0)    收藏  举报  来源