割点和桥
标题1
一、定义
连通度(百度百科):
割点集合:无向图中,删除集合中所有的点以及点所连接的边后,无向图不连通的集合
割边集合:无向图中,删除集合中所有的边后,无向图不连通的集合
点连通度:无向图中,割点集合中最小集合中的元素个数
边连通度:无向图中,割边集合中的最小集合的元素个数
双联通图,割点,和桥:
双联通图:无向图中,边连通度或者点连通度大于1
桥:图的边连通度为1,割边集合中的唯一元素集合中的元素为桥
割点:图的点连通度为1,割点集合中的唯一元素集合中的元素为桥
双联通分量:
在图G所有子图中,如果G'是双联通子图,G'不是任何一个双连通子图的真子集,则G'是一个极大双联通子图,特殊的点双联通分量又叫做块
二、Tagan
割点的条件:
(满足一个即可)
1.如果u树根,那么如果u的子树超过1,则u为割点
2.如果u不为树根,且满足存在(u,v)为树枝边(u为v在搜索树中的父亲),并使得DFN(u)<=low(v)(删去u以及v以及v的子树不能到达u的祖先)
#include<stdio.h> #include<stdlib.h> #define FORa(i,s,e) for(int i=s;i<=e;i++) #define FORs(i,s,e) for(int i=s;i>=e;i--) using namespace std; const int N=100,M=200; int n,m,num_edge,head[N+1],dfn[N+1],low[N+1],index; bool cut[N+1]; struct Edge{ int next,to; }edge[2*M+2]; inline void Add_edge(int from,int to){ edge[++num_edge]=(Edge){head[from],to},head[from]=num_edge; } inline int min(int fa,int fb){return fa<fb?fa:fb;} void Tarjan(int x) { low[x]=dfn[x]=++index; int cnt=0; for(int i=head[x];i;i=edge[i].next) { if(!dfn[edge[i].to]) { cnt++; Tarjan(edge[i].to),low[x]=min(low[x],low[edge[i].to]); if(cnt>1&&x==1||dfn[x]<=low[edge[i].to]) cut[x]=1; } else low[x]=min(low[x],dfn[edge[i].to]); } } int main() { int from,to; scanf("%d%d",&n,&m); FORa(i,1,m) { scanf("%d%d",&from,&to); Add_edge(from,to),Add_edge(to,from); } Tarjan(1); FORa(i,1,n) printf("%d ",cut[i]); return 0; } /*7 8 1 5 1 4 4 5 1 2 1 3 4 3 3 7 3 6*/
桥的条件:
(满足一个即可)
1.如果u树根,那么如果u的子树超过1,则u为割点
2.如果u不为树根,且满足存在(u,v)为树枝边(u为v在搜索树中的父亲),并使得DFN(u)<=low(v)(删去u以及v以及v的子树不能到达u的祖先)
#include<stdio.h> #include<stdlib.h> #define FORa(i,s,e) for(int i=s;i<=e;i++) #define FORs(i,s,e) for(int i=s;i>=e;i--) using namespace std; const int N=1000,M=2000; int n,m,num_edge=1,top,index,head[N+1],dfn[N+1],low[N+1],fa[N+1]; //注意num_edge是从奇数开始的,因为只有从奇数开始,第一个数就是偶数,那么偶数^1就是比它大1的奇数了 bool bridge[M+1]; struct Edge{ int next,to; }edge[M+1]; inline void Add_edge(int from,int to) { edge[++num_edge]=(Edge){head[from],to},head[from]=num_edge; } inline int min(int fa,int fb){return fa<fb?fa:fb;} inline void Tarjan(int u) { dfn[u]=low[u]=++index; for(int i=head[u],v;i;i=edge[i].next) { v=edge[i].to; if(i==(fa[u]^1)) continue; if(!dfn[v]) { fa[v]=i,Tarjan(v); low[u]=min(low[u],low[v]); if(low[v]>dfn[u]) bridge[fa[v]]=bridge[fa[v]^1]=1; } else low[u]=min(low[u],dfn[v]); } } int main() { int from,to; scanf("%d%d",&n,&m); FORa(i,1,m) { scanf("%d%d",&from,&to); Add_edge(from,to),Add_edge(to,from); } Tarjan(1); FORa(i,1,num_edge) if(bridge[i]) { printf("%d",edge[i].to); printf(" %d\n",edge[i+1].to); } return 0; } /*12 16 12 11 11 8 11 10 8 10 10 9 9 8 9 7 7 6 5 7 6 5 6 4 6 3 4 3 2 3 3 2 4 1*/
三、求双联通分量
点双联通分量:
在求割点的时候,每一次找到树枝边和后向边,就将边加入到栈中,如果发现点u是割点,将栈中边(u,v)以上的边都弹出栈。这些边与边的点组成的图就是一个点强连通分量
边双联同分量
在求桥的时候,求出桥(u,v)删去所有桥,剩下的连通块都是一个双联通分量,不包含桥
将有桥的连通图变成双联通图:
删去桥。将剩下的连通图缩成一个点,因为每一个连通图是一个边双联通分量,在边双联通图中加边不会减少桥的数目,所以先缩点
我们可以看如果没有割边,根据树的性质,则将叶子结点连在一起就行,所以要连接的边数为(叶子结点+1)/2