割边+割点 学习心得

发布时间 2023-10-13 14:22:36作者: DZhearMins

先背诵 tarjan 板子

#include<bits/stdc++.h>  
using namespace std;  
#define N 10005  
#define M 100005  
int tot,first[N],nxt[M],to[M];  
void add(int x,int y){  
    nxt[++tot]=first[x];  
    first[x]=tot;  
    to[tot]=y;  
}  
int n,m;  
bool ins[N];  
int dfn[N],low[N],dfc,stk[N],stail,idx,siz;  
void tarjan(int x){  
    dfn[x]=low[x]=++dfc;  
    stk[++stail]=x;  
    ins[x]=1;  
    for(int e=first[x];e;e=nxt[e]){  
        if(!dfn[to[e]]){  
            tarjan(to[e]);  
            low[x]=min(low[x],low[to[e]]);  
        }else if(ins[to[e]]){  
            low[x]=min(low[x],dfn[to[e]]);  
        }  
    }  
    if(dfn[x]==low[x]){  
        siz=0;  
        while(stk[stail]!=x){  
            --stail;  
            ++siz;  
        }  
        --stail;  
        if(siz>=1)++idx;  
    }  
    return;  
}  
int main(){  
    memset(ins,0,sizeof ins);  
    int x,y;  
    scanf("%d %d",&n,&m);  
    for(int i=1;i<=m;++i){  
        scanf("%d %d",&x,&y);  
        add(x,y);  
    }  
    for(int i=1;i<=n;++i){  
        if(!dfn[i])tarjan(i);  
    }  
    printf("%d\n",idx);  
    return 0;  
}

割点

相当于在 tarjan 找完新点以后判断一下新的点能不能通过另一条路径走回来,如果不能,那就是割点。

image

如图所示,下面部分的点,除了红色路径以外就不能走回上面 \(dfn\le4\) 的部分了,因此浅蓝色的点是割点。

image

如图所示,如果加了一条蓝色边,那么绿色点就可以通过蓝色边走回来,因此绿色点的 \(low\) 一定小于等于 \(4\) ,即能回到浅蓝色点及其之前的点上去,因此浅蓝色点不是割点。

需要注意的是,每次遍历的根节点不适用于这套规则,需要特判。而根节点是割点的条件是 出度 \(\ge2\) ,很好理解。

/*  
  bug:low[u] **>= ** 而不是 > dfn[x],因为走得到自己也算。  
 */  
#include<bits/stdc++.h>  
using namespace std;  
#define N 20005  
#define M 200005  
int tot,fir[N],nxt[M],to[M];  
int n,m,ans;  
int dfn[N],low[N],stk[N],stl,idx,grp[N],spc[N],dfc,root;  
bool ins[N];  
void add(int x,int y){  
    nxt[++tot]=fir[x];  
    fir[x]=tot;  
    to[tot]=y;  
}  
void tarjan(int x,int fa){  
    dfn[x]=low[x]=++dfc;  
    ins[x]=true;  
    stk[++stl]=x;  
    int scnt=0;  
    for(int e=fir[x];e;e=nxt[e]){  
        int u=to[e];  
        if(u==fa)continue;  
        if(!dfn[u]){  
            ++scnt;  
            tarjan(u,x);  
            low[x]=min(low[x],low[u]);  
            if(low[u]>=dfn[x]&&x!=root){  
                spc[x]=1;  
            }  
        }else if(ins[u]){  
            low[x]=min(low[x],dfn[u]);  
        }  
    }  
    if(dfn[x]==low[x]){  
        for(int u;stk[stl]!=x;--stl){  
            u=stk[stl];  
            ins[u]=0;  
        }  
        ins[x]=0;  
        --stl;  
    }  
    if(x==root){  
        if(scnt>1)spc[x]=1;  
    }  
    return;  
}  
int main(){  
    int x,y;  
    scanf("%d %d",&n,&m);  
    for(int i=1;i<=m;++i){  
        scanf("%d %d",&x,&y);  
        add(x,y);  
        add(y,x);  
    }  
    for(int i=1;i<=n;++i){  
        if(!dfn[i]){  
            root=i;  
            tarjan(i,0);  
        }  
    }  
    for(int i=1;i<=n;++i){  
        if(spc[i])++ans;  
    }  
    printf("%d\n",ans);  
    for(int i=1;i<=n;++i){  
        if(spc[i])  
        printf("%d ",i);  
    }  
    return 0;  
}

割边

比割点多一个条件:

image

如图,割边不能让另一个边连到浅蓝色节点上,因此判断的条件不是 \(dfn_x \le low_x\) 而是 \(dfn_x < low_x\)

image

这样就可以了。

并且,算割边不用判断根节点,因为根节点不是一条边

#include<bits/stdc++.h>  
using namespace std;  
#define N 200  
#define M 10005  
int tot,fir[N],nxt[M],to[M];  
int dfn[N],dfc,low[N],idx,grp[N],stk[N],stl;  
pair<int,int>sln[N];  
int n,m,slc;  
bool ins[N];  
void add(int x,int y){  
    nxt[++tot]=fir[x];  
    fir[x]=tot;  
    to[tot]=y;  
}  
void tarjan(int x,int fa){  
    dfn[x]=low[x]=++dfc;  
    ins[x]=1;  
    stk[++stl]=x;  
    for(int e=fir[x];e;e=nxt[e]){  
        int u=to[e];  
        if(u==fa)continue;  
        if(!dfn[u]){  
            tarjan(u,x);  
            low[x]=min(low[x],low[u]);  
            if(low[u]>dfn[x])sln[++slc]=make_pair(min(x,u),max(x,u));  
        }else if(ins[u]){  
            low[x]=min(low[x],dfn[u]);  
        }  
    }  
    if(dfn[x]==low[x]){  
        for(int u;stk[stl]!=x;--stl){  
            u=stk[stl];  
            ins[u]=0;  
        }  
        ins[x]=0;  
        --stl;  
    }  
    return;  
}  
int main(){  
    int x,y;  
    memset(ins,0, sizeof ins);  
    scanf("%d %d",&n,&m);  
    for(int i=1;i<=m;++i){  
        scanf("%d %d",&x,&y);  
        add(x,y);  
        add(y,x);  
    }  
    for(int i=1;i<=n;++i){  
        if(!dfn[i])tarjan(i,0);  
    }  
    sort(sln+1,sln+slc+1);  
    for(int i=1;i<=slc;++i){  
        printf("%d %d\n",sln[i].first,sln[i].second);  
    }  
    return 0;  
}