zoukankan      html  css  js  c++  java
  • 双连通分量

    双连通分量

    前置知识

    基本概念

    双连通分量又分点双连通分量和边双连通分量两种。若一个无向图中的去掉任意一个节点(一条边)都不会改变此图的连通性,即不存在割点(桥),则称作点(边)双连通图。一个无向图中的每一个极大点(边)双连通子图称作此无向图的点(边)双连通分量。求双连通分量可用Tarjan算法。——百度

    简单来说,一个没有割点(桥)的无向图就是点(边)双连通分量。

    以下图为例:
    Mf2eAS.png
    对于这个图来说一共有2个点双连通分量,分别是:

    (1,2,5),(2,3,4)
    

    有3个边双连通分量,分别是:

    (1,2,5),(2,3,4),(1,2,3,4,5)
    

    点双连通分量

    首先对于一个点双连通分量来说,里面一定没有割点。

    所以说当我们查找到一个割点u的时候,就将以u为根的搜索子树内 还不属于任何一个点双连通分量 且 不为割点 或 为割点 但 与那些以u为根的搜索子树内 还不属于任何一个点双连通分量 且 不为割点 的点 直接连通的点 为一个点双连通分量。

    简单来说,就是当我们查找到一个割点的时候,我们像强连通分量那样让仍在栈内的点成为一个点双连通分量。

    最后附上代码:

    代码

    void tarjan(int u,int fa) {
    	dfn[u]=low[u]=++index;
    	s.push(u);
    	for(int i=0; i<g[u].size(); i++) {
    		int v=g[u][i];
    		if(v==fa)continue;
    		if(!dfn[v]) {
    			tarjan(v,u);
    			low[u]=min(low[u],low[v]);
    			if(low[v]>=dfn[u]){
    				cnt++;
    				int d;
    				do{
    					d=s.top();
    					s.pop();
    					gcc[d]=cnt;
    				}while(d!=v);//将目前在栈中的点都归于当前的点双连通分量中去
    				gcc[u]=cnt;//将该点也归于当前的点双连通分量中去
    			}
    		} else {
    			low[u]=min(low[u],dfn[v]);
    		}
    	}
    }
    

    完美完结

    边双连通分量

    我们只需要将这个图中所有的桥都求出来,再将它们删除,此时图中每一个连通块都是一个边双连通分量

    代码

    void tarjan(int u,int fa) {
    	dfn[u]=low[u]=++index;
    	for(int i=head[u]; i!=-1; i=a[i].next) {//在这里,由于我们要将桥都记录下来,所以选择使用前向星
    		int v=a[i].to;
    		if(v==fa)continue;
    		if(!dfn[v]) {
    			tarjan(v,u);
    			low[u]=min(low[u],low[v]);
    			if(low[v]>dfn[u])vis[i]=true;//这条边为桥
    		} else {
    			low[u]=min(low[u],dfn[v]);
    		}
    	}
    }
    void dfs(int u) {//遍历删去桥的图
    	ecc[u]=cnt;//记录
    	for(int i=head[u]; i!=-1; i=a[i].next) {
    		int v=a[i].to;
    		if(!ecc[v]&&!vis[i])dfs(v);
    	}
    }
    

    例题

    P3225 矿场搭建

    这道题可以用割点过,但是思路稍显复杂。
    用点双连通分量思路会清晰很多。
    详细可以看这篇题解

    hdu4612 Warm up

    Problem Description

      N planets are connected by M bidirectional channels that allow instant transportation. It's always possible to travel between any two planets through these channels.
      If we can isolate some planets from others by breaking only one channel , the channel is called a bridge of the transportation system.
    People don't like to be isolated. So they ask what's the minimal number of bridges they can have if they decide to build a new channel.
      Note that there could be more than one channel between two planets.

    Input

      The input contains multiple cases.
      Each case starts with two positive integers N ((2 leq Nleq2 imes10^5)) and M ((1 leq Mleq10^6)) , indicating the number of planets and the number of channels.
      Next M lines each contains two positive integers A and B, indicating a channel between planet A and B in the system. Planets are numbered by 1 to N.
      A line with two integers '0' terminates the input.

    Output

     For each case, output the minimal number of bridges after building a new channel in a line.

    Sample Input

    (4) (4)
    (1) (2)
    (1) (3)
    (1) (4)
    (2) (3)
    (0) (0)

    Sample Output

    (0)

    题目大意

    对于一个联通的无向图,问添加一条边后至少剩下几个桥

    解题思路

    先用边双连通分量缩点,树的直径即为最多减少的桥数

    代码

    #include<iostream>
    #include<cstring>
    #include<vector>
    using namespace std;
    struct ed {
    	int to,next;
    } a[2000005];
    int t,head[200005];
    int n,m,cnt;
    int dfn[200005],low[200005],scc[200005],in;
    vector<int>g[200005];
    bool cutedge[2000005];
    void tarjan(int u,int fa) {//tarjan模板
    	dfn[u]=low[u]=++in;
    	for(int i=head[u]; i!=-1; i=a[i].next) {
    		if((i^1)==fa)continue;
    		int v=a[i].to;
    		if(!dfn[v]) {
    			tarjan(v,i);
    			low[u]=min(low[u],low[v]);
    			if(low[v]>dfn[u])cutedge[i]=cutedge[i^1]=true;
    		} else {
    			low[u]=min(low[u],dfn[v]);
    		}
    	}
    }
    void dfs(int u) {//遍历
    	scc[u]=cnt;
    	for(int i=head[u]; i!=-1; i=a[i].next) {
    		int v=a[i].to;
    		if(!scc[v]&&!cutedge[i])dfs(v);
    	}
    }
    void add(int u,int v) {//前向星加边
    	a[t].to=v;
    	a[t].next=head[u];
    	head[u]=t++;
    }
    void clean() {//初始化
    	for(int i=0;i<=200000;i++)g[i].clear();
    	memset(cutedge,false,sizeof(cutedge));
    	memset(head,-1,sizeof(head));
    	memset(dfn,0,sizeof(dfn));
    	memset(low,0,sizeof(low));
    	memset(scc,0,sizeof(scc));
    	memset(d1,0,sizeof(scc));
    	memset(d2,0,sizeof(scc));
    	memset(a,0,sizeof(a));
    	ans=0,cnt=0,in=0,t=0;
    }
    int ans,d1[200005],d2[200005];
    int dia(int u,int s,int fa) {//树的直径
    	d1[u]=s;
    	for(int i=0; i<g[u].size(); i++) {
    		int v=g[u][i];
    		if(v==fa)continue;
    		int l=dia(v,s+1,u)+1;
    		if(l>=d1[u])d2[u]=d1[u],d1[u]=l;
    		else if(l>=d2[u])d2[u]=l;
    	}
    	return d1[u];
    }
    int main() {
    	while(cin>>n>>m) {
    		if(n==0&&m==0)break;
    		clean();
    		while(m--) {
    			int u,v;
    			cin>>u>>v;
    			add(u,v),add(v,u);
    		}
    		for(int i=1; i<=n; i++) {
    			if(!dfn[i])tarjan(i,-1);//求桥
    		}
    		for(int i=1; i<=n; i++) {
    			if(!scc[i]){
    				cnt++;
    				dfs(i);//求边双连通分量
    			}
    		}
    		for(int u=1; u<=n; u++) {//缩点
    			for(int i=head[u]; i!=-1; i=a[i].next) {
    				int v=a[i].to;
    				if(scc[u]!=scc[v])g[scc[u]].push_back(scc[v]);
    			}
    		}
    		dia(1,0,-1);//在缩完点后的树上跑树的直径
    		for(int i=1; i<=cnt; i++)ans=max(ans,d1[i]+d2[i]);
    		cout<<cnt-1-ans<<endl;//总边数-树的直径=至少剩下几个桥
    	}
    	return 0;
    }
    
  • 相关阅读:
    解决mybatis xml文件代码提示
    SVN cleanup failed–previous operation has not finished; run cleanup if it was interrupted
    有36辆自动赛车和6条跑道,没有计时器的前提下,最少用几次比赛可以筛选出最快的三辆赛车?
    mybatis如何在控制台打印执行的sql语句
    Ionic2如何下拉刷新和上拉加载
    Ionic 如何把左上角的按钮去掉?
    Ionic1与Ionic2
    java的四种引用,强弱软虚
    equals变量在前面或者在后面有什么区别吗?这是一个坑点
    Java基础—复用类
  • 原文地址:https://www.cnblogs.com/ezlmr/p/12234498.html
Copyright © 2011-2022 走看看