边双连通分量/割边求法
边双连通分量/割边求法
题目参考 洛谷P1656
算法标签:无向图割边、tarjan强连通分量
如果点u通过一条边m到达v后,v无法在不经过m回到u,那么m这条边称之为割边,u和v分别属于两个不同的双连通分量。易知,两个点是否处于一个双连通分量是等价关系
如何求割边?
记录每个点使用最多一条返祖边能到达的最远祖先,用low[]表示
记录通过dfs访问点的次序序号,用dfn[]表示
-
如果遍历u出发的边(忽略回去的反向边)到达v时,v已经访问过,说明u可以走这一条返祖边直接回到祖先v,因此更新low[u]=min(low[u],dfn[v]);
- 否则先dfs(v),尝试通过v回到更远的祖先,即low[u]=min(low[u],low[v])
-
如果low[v](从v走一条返祖边回到的最远祖先)>dfn[u],那就是说v不能通过其他方式回来u(或者u的祖先)了,也就是u,v是一条割边
如果题目不保证不存在重边,我们是不可以dfs(int u,int fa){```if(v==fa) continue; ```}的因为v可能通过u、v之间的其他重边回到u,
这种情况下,我们在使用链式前向星建图时初始化 边数起点cnt=1,这样每一条边和其反向边的边序号都是^1的关系,2^1=3,3^1=2。dfs里面记录边序号lst,(u->v的一条边e[i]的) i==lst^1时我们就不要访问
#include<iostream>
#include<algorithm>
using namespace std;
#define pii pair<int,int>
#define fi first
#define se second
const int N=310,M=5005;
int cnt0=1;//链式前向星的边数,初始化为1
int head[N];
struct Edge{
int to,nxt;
}e[M<<1];
void add_edge(int x,int y){
e[++cnt0].to=y;
e[cnt0].nxt=head[x];
head[x]=cnt0;
e[++cnt0].to=x;
e[cnt0].nxt=head[y];
head[y]=cnt0;
}
int n,m;
int st[N],top;//保存强连通分量时的栈
int idx;//强连通分量的编号
int bel[N];//每个点所属哪个双连通分量
int low[N];//最多走一条返祖边回到的最远祖先的dfs序号
int dfn[N],cnt;//dfs序号
int anscnt;
pii ans[M<<1];//存割边
void dfs(int u,int lst){
low[u]=dfn[u]=++cnt;
st[++top]=u;
for(int i=head[u];i;i=e[i].nxt){
if(i==(lst^1)) continue;//不走反向边
int v=e[i].to;
if(dfn[v]==0){
dfs(v,i);
low[u]=min(low[u],low[v]);
if(low[v]>dfn[u]){//v走最多一条其他返祖边回不来u,也即存在割边
ans[anscnt++]=make_pair(min(u,v),max(u,v));
}
}else{
low[u]=min(low[u],dfn[v]);
}
}
if(low[u]==dfn[u]){//相等则说明u是新强连通分量的头(深度最小)
int v;++idx;
do{
v=st[top--];
bel[v]=idx;
}while(v!=u);
}
}
int main(){
cin>>n>>m;
for(int i=0,x,y;i<m;i++){
cin>>x>>y;
add_edge(x,y);
}
for(int i=1;i<=n;i++){
if(!dfn[i]) dfs(i,-1);
}
sort(ans,ans+anscnt);
for(int i=0;i<anscnt;i++) cout<<ans[i].fi<<' '<<ans[i].se<<endl;
cout<<endl;
return 0;
}

浙公网安备 33010602011771号