Tarjan割点

更新日志 update 2024/10/16:提供例题代码

思路

首先,我们要理解什么是割点:

无向图中,如果去掉这个点,会使强连通分量数量增多的点,即为割点。

说人话,就是没了这个点,这个连通图就不再连通了。

很轻易就能想到,假如某一个连通块只有一个点通向外部,那么点就是一个割点。

通过这个概念,我们不难想到\(Tarjan\)算法求强连通分量时,同样也是通过看当前dfs生成树子树中有无节点通向外部来判定强连通分量的。在这里,我们可以使用同样的方法。

具体操作见求强连通分量部分

更具体地,假如这个点的某一棵子树中所有节点最高只能通向此节点,那么这个节点就是作用于这棵子树的割点。

例外的,这个条件并不适用于dfs树根节点,因为其有可能在原图中是一个“末端”,所以我们要额外判定其是否有两个及以上子节点。

在dfs树的角度分析,之所以上面判定条件满足,是因为这个点可以把原dfs树分成两个部分(它所在的子树与整棵树剩下的部分),而根节点并没有父节点,无法确保删除它之后整棵树能被分成两个部分,除非它有两棵及以上的子树。

细节

与求强连通分量不同的是,这是一个无向图,故而不存在\(单向奔赴\),故而所有能通向的点全都与其位于同一棵子树中,无需判断它是否还在“栈”中。

另外,判断这个点是否是一个割点,并不是看它的low是否大于等于它的dfn(很容易和求强连通分量搞混),而是看是否存在一棵low大于等于这个节点dfn的子树,才能说明它有能力把整个图至少分成两部分。

因此,由于一个节点有多棵子树,所以如果不额外操作的话,就会把这个点重复判定为割点,如果是用vector之类储存的话,需要加一个标记,标记这个节点是否已经被判定为割点,就无需重复加入了。

在特判根节点时也不要忘记判断flag标记。

需要注意的是,严格意义来说,子树的low不应该由到它的原边得到它父节点的dfn,但由于对结果无影响,故而无需特别讨论。(与割边区分!)

模板

int dcnt;
int dfn[N],low[N];
bool flag[N];
veci spl;//储存割点
void tarjan(int rt,int fa){//注:这里定义根节点的父亲为它本身,用于判定
    dfn[rt]=low[rt]=++dcnt;
    int ccnt=0;flag[rt]=false;
    for(int e=hd[rt];e;e=ne[e]){
        int nxt=to[e];
        if(!dfn[nxt]){
            ccnt++;
            tarjan(nxt,rt);
            low[rt]=min(low[rt],low[nxt]);
            if(!flag[rt]&&low[nxt]>=dfn[rt]&&rt!=fa){//额外判断根节点
                spl.push_back(rt);
                flag[rt]=true;
            }
        }else low[rt]=min(low[rt],dfn[nxt]);
    }
    if(!flag[rt]&&rt==fa&&ccnt>=2){//额外判断根节点
        spl.push_back(rt);
        flag[rt]=true;
    }
}

例题

封锁

代码

前注:非题解,不做详细讲解

此题并非完全意义上的求割点,所以与模板有所不同,如无需特判根节点。

原因就是它的目的不是求割点。

#include<bits/stdc++.h>
using namespace std;

#define fir first
#define sec second
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
typedef pair<ll,int> pli;
typedef pair<int,ll> pil;
typedef pair<ll,ll> pll;
typedef pair<int,pii> pip;
typedef vector<int> veci;
typedef vector<pii> vecp;
typedef priority_queue<int> bghp;
typedef priority_queue<int,vector<int>,greater<int> > lthp;

const int mod=998244353;
const int N=1e5+5,M=5e5+5;

int n,m;

int cnt;
int hd[N],ne[M*2],to[M*2];
void adde(int a,int b){
    to[++cnt]=b;
    ne[cnt]=hd[a];
    hd[a]=cnt;
}

int dcnt;
int dfn[N],low[N];
int scnt;
int scc[N],siz[N];
ll ans[N];
void tarjan(int rt,int fa){
    dfn[rt]=low[rt]=++dcnt; 
    int dosiz=0;
    bool flag=false;
    siz[rt]=1;
    ans[rt]=2*(n-1);
    for(int e=hd[rt];e;e=ne[e]){
        int nxt=to[e];
        if(!dfn[nxt]){
            tarjan(nxt,rt);
            low[rt]=min(low[rt],low[nxt]);
            siz[rt]+=siz[nxt];
            if(low[nxt]>=dfn[rt]){
                flag=true;
                ans[rt]+=(ll)2*dosiz*siz[nxt];
                dosiz+=siz[nxt];
            }
        }else low[rt]=min(low[rt],dfn[nxt]);
    }
    ans[rt]+=(ll)2*dosiz*(n-dosiz-1);
}

int main(){
    // freopen(".in","r",stdin);
    // freopen(".out","w",stdout);
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>n>>m;
    int u,v;
    for(int i=1;i<=m;i++){
        cin>>u>>v;
        adde(u,v);
        adde(v,u);
    }
    for(int i=1;i<=n;i++){
        if(!dfn[i])tarjan(i,i);
    }
    for(int i=1;i<=n;i++){
        cout<<ans[i]<<"\n";
    }
    return 0;
}
posted @ 2024-10-16 13:59  HarlemBlog  阅读(3)  评论(0编辑  收藏  举报