写在前面
以下内容均适用于无向图。
基本概念
割点:删去后原图不连通的点。
桥:删去后原图不连通的边。
点双连通图:删去任意一点仍连通的图。
边双连通图:删去任意一边仍连通的图。
双连通分量:图的极大双连通子图。
求割点与桥:Tarjan算法
割点判定法则
一个顶点是割点的充要条件为以下两个之一:
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| void dfs(int u,int fa){ dfn=low=++tim; fl=1; int sz=0; for(int i=lst;i;i=e.lst){ int v=e.t; if(!fl){ dfs(v,u); sz++; if(u==root&&sz>1)cut=1; low=min(low,low); if(u!=root&&dfn<=low)cut=1; } else{ low=min(low,dfn);//无向图,不用栈 } } if(cut)tot++; } void tarj(){ for(int i=1;i<=n;i++){ if(!fl)root=i,dfs(i,0); } }
|
桥判定法则
一条边是桥的充要条件是:
- 搜索树上存在 $u$ 的一个儿子 $v$ 满足 $DFN(u) < Low(v)$。
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| void dfs(int u,int pre){ dfn=low=++tim; fl=1; for(int i=lst;i;i=e.lst){ int v=e.t; if(!fl){ dfs(v,i); low=min(low,low); if(dfn<low){ e.bridge=e.bridge=1; tot++; fe=e; if(fe.f>fe.t)swap(fe.f,fe.t); } } else if(i!=(pre^1)){//对重边进行特判 low=min(low,dfn); } } } void tarj(){ for(int i=1;i<=n;i++){ if(!fl)dfs(i,0); } }
|
求双连通分量
边双连通分量的求法
求出所有桥后,把桥边删除,原图变成多个连通块,每个连通块都是一个边双连通分量。具体实现可以DFS一遍,遇到桥就跳过。
代码实现
1 2 3 4 5 6 7 8 9 10
| vector<int> ans; void dfs2(int u){ fl[u]=1; ans.push_back(u); for(int i=lst[u];i;i=e[i].lst){ int v=e[i].t; if(fl[v]||e[i].brg)continue; dfs2(v); } }
|
点双连通分量的求法
效仿强连通分量,维护一个栈。第一次访问节点时入栈。当割点判定法则中 $DFN(u) \le Low(v)$ 成立时,无论该点是否为根,都要从栈顶不断弹出节点直至 $v$ 出栈,此时弹出的所有结点与 $u$ 一起构成一个点双连通分量。注意:孤立点也属于点双连通分量!要特判!
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| void dfs(int u){ fl=1; dfn=low=++tim; stk=u; int sz=0; for(int i=lst;i;i=e.lst){ int v=e.t; if(!fl){ dfs(v); sz++; low=min(low,low); if(u==rt&&sz>1)ct=1; if(u!=rt&&dfn<=low)ct=1; if(dfn<=low){ hd=1; while(stk!=v){ ans.push_back(stk); vis=1; top--; } ans.push_back(v); ans.push_back(u); vis=vis=1; top--; } } else{ low=min(dfn,low); } } } void tarjan(){ for(int i=1;i<=n;i++){ if(!fl)rt=i,dfs(i); } }
|