割点
发表于:2023-04-30 | 分类: 图论
字数统计: 872 | 阅读时长: 3分钟 | 阅读量:

YlqY_{lq}

洛谷割点模板

何为割点?
简单来说是在一个无向图中,若删除该点,则该无向图将会被分为两部分

如上图所示,结点DD是一个割点,但是ABCEFA、B、C、E、F不是割点

此外在这里我们引入dfn[i],low[i]dfn[i],low[i] 两个概念
dfn[i]:遍历到该结点dfn[i]:遍历到该结点ii时的时间,称之为——时间戳时的时间,称之为——时间戳
low[i]:不通过父结点low[i]:不通过父结点ii所能到达的最小的时间戳的值,即最小回溯值所能到达的最小的时间戳的值,即最小回溯值
例如对上图我们从AA结点开始遍历,标记时间戳是这样的

再之,我们求得每个结点的lowlow值如下

我们可以发现,对结点EFE、F而言,他们无法回溯到比自己更小的dfndfn值,即显然他们的父节点DD为一个割点。
我们用O(n)O(n)的复杂度遍历图中的每一个结点,并对其的dfnlowstdfn、low、st进行初始化

1
2
3
st[u] = 1;
dfn[u] = low[u] = ++ in;
//我们以一个全局变量in来表示记录到该节点的时间

结点为割点的两种情况:
1、当前结点uu为根结点,且存在两个以上的子结点
2、当前结点uu不为根节点,且对于其连接的子结点vv而言,low[v]>=dfn[u]low[v]>=dfn[u],说明其儿子vv无法通过其他结点跑到uu结点之前
因此,当我们遍历发现该结点为标记过时

1
2
3
4
5
6
7
8
9
10
if(!st[v]){
num ++; //累计结点u的子结点数量
tarjan(v, u); //遍历v结点出发的图
low[u] = min(low[u], low[v]); //更新low[u]
if(fa != u && low[v] >= dfn[u] && !ok[u]){ //倘若该节点不是根节点,且满足割点条件
ok[u] = 1;
ans ++;
}
}

反之该结点标记过了,并且结点没有往回跑的话,我们如果继续用low[u]=min(low[u],low[v])low[u]=min(low[u],low[v])的话,
以下图为例,

当我们在从DD跑到EE时,如果对EElow[E]=min(low[E],low[C])low[E]=min(low[E],low[C])的话,那么low[E]=low[C]=1low[E]=low[C]=1,此时不满足显然的割点条件,因此
我们需要对标记过的连接值取low[u]=min(low[u],dfn[v])low[u]=min(low[u],dfn[v])
至此我们得出该题的解决方案.

代码

#include "bits/stdc++.h"
#define rep(i, n, m) for(int i = n; i <= m; ++i)
using namespace std;
const int N = 8e5 + 10;
int dfn[N], low[N], st[N], ok[N], in = 0;
int h[N], ne[N], e[N], cnt, ans = 0;
void add(int a, int b){
     e[cnt] = b, ne[cnt] = h[a], h[a] = cnt++;
}
void tarjan(int u, int fa){
    st[u] = 1;
    dfn[u] = low[u] = ++ in;
    int num = 0;
    for(int i = h[u]; ~i; i = ne[i]){
        int v = e[i];
        if(!st[v]){
            num ++;
            tarjan(v, u);
            low[u] = min(low[u], low[v]);
            if(fa != u && low[v] >= dfn[u] && !ok[u]){
                ok[u] = 1;
                ans ++;
            }
        }else if(v != fa){
            low[u] = min(low[u], dfn[v]);
        }
    }
    if(u == fa && num >= 2 && !ok[u]){
        ok[u] = 1;
        ans ++;
    }
}
int main()
{
    //ios::sync_with_stdio(0); cin.tie(0);
    memset(h, -1, sizeof h);
    memset(ok, 0, sizeof ok);
    int n, m; cin >> n >> m;
    rep(i, 1, m){
        int u, v; cin >> u >> v;
        add(u, v), add(v, u);
    }
    rep(i, 1, n){
        if(!st[i]){
            in = 0;
            tarjan(i, i);
        }
    }
    cout << ans << '\n';
    rep(i, 1, n){
        if(ok[i]) cout << i << ' ';
    }
    return 0;
}

上一篇:
傅里叶变换
下一篇:
烂题三道