一、概念
概念:
1.桥: 如果在图G中删去一条边e后,图G的连通分支数增加,即W(G-e)>W(G),则称边u为G的桥,又称割边或关节边。
2.割点:如果在图G中删去一个结点u后,图G的连通分枝数增加,即W(G-u)>W(G),则称结点u为G的割点,又称关节点。
3.点双连通分量:不含割点的连通子图
4.边双连通分量:不含割边的连通子图
性质:
1.边双连通分量中,任意两点都在某个边环中。(任意两点不一定在点环中)
2.点双连通分量中,任意两点都在某个点环中。
3.点双连通分量不一定是边双连通分量,边双连通分量不一定是点双连通分量。
二、求桥模板
#define N 100100 #define M 200110 #define INF 0x3fffffff struct node { int from,to,next; }edge[2*M]; vector<int>mat[N];//用来建新图. int n,m; int savex[M],savey[M]; int cnt,pre[N]; bool mark[2*M]; bool save[2*M]; int low[N]; int mylink[N]; void add_edge(int u,int v) { edge[cnt].to=v; edge[cnt].from=u; edge[cnt].next=pre[u]; pre[u]=cnt++; } void dfs(int s,int num)//寻找桥 { low[s] = num; int mi=num; for(int p=pre[s];p!=-1;p=edge[p].next) { int v=edge[p].to; if(mark[p]==1) continue; if(low[v]==-1) { mark[p]=1; mark[p^1]=1; dfs(v,num+1); if(low[v]>num) // 说明edge[p]是桥 { save[p]=1; save[p^1]=1; } } mi=min(mi,low[v]); } low[s]=mi; } void dfs1(int s,int num)//缩点 { mylink[s]=num; mark[s]=1; for(int p=pre[s];p!=-1;p=edge[p].next) { int v=edge[p].to; if(mark[v]==1||save[p]==1) continue; dfs1(v,num); } } void init() { cnt=0; memset(pre,-1,sizeof(pre)); } int main() { init(); //构图 scanf("%d%d",&n,&m); for(int i=0;i<m;i++) { int x,y; scanf("%d%d",&x,&y); savex[i] = x; savey[i] = y; add_edge(x,y); add_edge(y,x); } //tarjan 找桥。 save里面记录了边是否是桥的信息 memset(low,-1,sizeof(low)); memset(mark,0,sizeof(mark)); memset(save,0,sizeof(save)); for(int i=1;i<=n;i++) { if(low[i]==-1) dfs(i,0); } //缩点 memset(mark,0,sizeof(mark)); int id=0; for(int i=1;i<=n;i++) { if(mark[i]==0) { dfs1(i,++id);//这里面还要处理 } } //每个点对应一个mylink,下列操作构建新图。 for(int i=0;i<m;i++) { mat[ mylink[ savex[i] ] ].push_back(mylink[ savey[i] ]); mat[ mylink[ savey[i] ] ].push_back(mylink[ savex[i] ]); } return 0; }
找到桥后,除去桥后的图中连通块即为边双连通分量。
三、割点模板
#define N 110 #define M N*N struct node { int to,next; }edge[2*M]; int n;//图中节点个数,边的个数 int pre[N],cnt; bool cutmark[N]; int vis[N]; int indx; int mylink[N]; int tn; void init() { memset(pre,-1,sizeof(pre)); cnt = 0; } void add_edge(int u,int v)//向图中插入一条无向边 { edge[cnt].to = v; edge[cnt].next = pre[u]; pre[u] = cnt++; edge[cnt].to = u; edge[cnt].next = pre[v]; pre[v] = cnt++; } void build_map()//构图 { init(); //然后就是加边 // char str[10010]; // getchar(); // while(gets(str) && str[0]!='0') // { // stringstream in(str); // int tmp; // int tu=0; // int flag=0; // while(in>>tmp) // { // if(flag == 0) // { // flag = 1; // tu = tmp; // } // else // { // add_edge(tu,tmp); // } // } // } } void dfs(int s,int fa,bool sign)//寻找割点 { vis[s] = ++indx; int _mi = indx;//这个结点所能到达的最上层 int _cutedge = 0; for (int p=pre[s]; p!=-1; p=edge[p].next) { int _v = edge[p].to; if(_v == fa) continue; if(vis[_v] == -1) { dfs(_v,s,sign|1); if(vis[_v] >= vis[s]) { _cutedge ++; } } _mi = min(_mi,vis[_v]); } vis[s] = _mi; if( (sign == 0&&_cutedge>1)||(sign == 1&&_cutedge>0) ) { cutmark[s] = 1;//为割点 } } void find_cutnode() { memset(cutmark,0,sizeof(cutmark)); memset(vis,-1,sizeof(vis)); indx = 0; for(int i=1;i<=n;i++) { if(vis[i] == -1) { dfs(i,-1,0); } } //是否为割点,存在cutmark中 } void dfs1(int s,int _id)//缩点用DFS { mylink[s] = _id; for(int p=pre[s];p!=-1;p=edge[p].next) { int _v = edge[p].to; if(mylink[_v] == 0 && cutmark[_v] == 0) { dfs1(_v,_id); } } } void shuodian()//缩点,对应为[1-tn]中的点 { tn = 0; memset(mylink,0,sizeof(mylink)); for(int i=1;i<=n;i++) { if(mylink[i] == 0 && cutmark[i] == 0) { dfs1(i,++tn); } } //然后把割点也算成一个点双连通块 for(int i=1;i<=n;i++) { if(cutmark[i] == 1) { mylink[i] = ++tn; } } //tn 表示点双连通块的个数. }
同理,除去割点后,剩余图中的连通块即为点双连通分量。