http://blog.csdn.net/geniusluzh/article/details/6619575
在说Tarjan算法解决桥和边双连通分量问题之前我们先来回顾一下Tarjan算法是如何求解强连通分量的。

       Tarjan算法在求解强连通分量的时候,通过引入dfs过程中对一个点访问的顺序dfsNum(也就是在访问该点之前已经访问的点的个数)和一个点可以到达的最小的dfsNum的low数组,当我们遇到一个顶点的dfsNum值等于low值,那么该点就是一个强连通分量的根。因为我们在dfs的过程中已经将点仍入到栈中,因此我们只需要将栈中的元素出栈直到遇到根,那么这些点就组成一个强连通分量。

        对于边双连通分量,我们需要先了解一些概念:

边连通度:使一个子图不连通所需要删除的最小的边数就是该图的边连通度。

桥(割边):当删除一条边就使得图不连通的那条边称为桥或者是割边。

边双连通分量:边连通度大于等于二的子图称为边双连通分量。

        理解了这些概念之后我们来看看Tarjan是如何求解边双连通分量的,不过在此之前我们先说说Tarjan是怎样求桥的。同样引入了dfsNum表示一个点在dfs过程中所被访问的时间,然后就是low数组表示该点最小的可以到达的dfsNum。我们分析一下桥的特点,删除一条边之后,那么如果dfs过程中的子树没有任何一个点可以到达父亲节点及父亲节点以上的节点,那么这个时候子树就被封死了,这条边就是桥。有了这个性质,也就是说当我们dfs过程中遇到一条树边a->b,并且此时low[b]>dfsNum[a],那么a-b就是一座桥。

        呵呵桥都求出来了,还怕边双连通分量吗?我们把所有的桥去掉之后那些独立的分量就是不同的边双连通分量,这个时候就可以按照需要灵活的求出边双连通分量了。

       下面附上POJ 3352的解题思路吧:

       这道题的意思是说,给你一个无向图,然后问你至少需要添加几条边,可以使整个图变成边双连通分量,也就是说任意两点至少有两条路可以互相连通。我们这样考虑这个问题,属于同一个边双连通分量的任意点是至少有两条通路是可以互相可达的,因此我们可以将一个边双连通分量缩成一个点。然后考虑不在边双连通分量中的点,通过缩点后形成的是一棵树。对于一个树型的无向图,需要添加(度为1的点的个数+1)/2条边使得图成为双连通的。这样问题就是变成缩点之后,求图中度为1的点的个数了。

       这个题目的条件给的很强,表示任意两个点之间不会有重边,因此我们可以直接经过Tarjan的low值进行边双连通分量的划分,最后求出度为1点数就可以解决问题了。如果是有重边的话,那么不同的low值是可能是属于同一个边双连通分量的,这个时候就要通过将图中的桥去掉然后求解边双连通分量,这个请见我的博客的另外一篇解题报告。

        下面贴上POJ 3352的ac代码,供网友们参考:
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <vector>
using namespace std;
const int Max=1010;
int top[Max],edge[Max][Max];//memset(top,0,sizeof(top));
int dfsNum[Max],dfsnum;//memset(dfsNum,0,sizeof(dfsNum)),dfsNum=1;
int low[Max];
int degree[Max];
int ans; void tarjan(int a,int fa)
{
dfsNum[a]=low[a]=++dfsnum;
for(int i=0;i<top[a];i++)
{
if(edge[a][i]!=fa)
{
if(dfsNum[edge[a][i]]==0)
{
tarjan(edge[a][i],a);
if(low[a]>low[edge[a][i]])
low[a]=low[edge[a][i]];
}
else
{
if(low[a]>dfsNum[edge[a][i]])
low[a]=dfsNum[edge[a][i]];
}
// if(low[edge[a][i]]>dfsNum[a])
// { // }
}
}
} int solve(int n)
{
int i,j;
int a,b;
for(i=1;i<=n;i++)
{
a=i;
for(j=0;j<top[i];j++)
{
b=edge[a][j];
if(low[a]!=low[b])
{
degree[low[a]]++;
degree[low[b]]++;
}
}
}
int leaves=0;
for(i=1;i<=n;i++)
{
if(degree[i]==2)
{
leaves++;
}
}
return (leaves+1)/2;
} int main()
{
int n,m;
int i,a,b;
while(scanf("%d %d",&n,&m)!=EOF)
{
memset(top,0,sizeof(top));
memset(degree,0,sizeof(degree));
for(i=0;i<m;i++)
{
scanf("%d %d",&a,&b);
edge[a][top[a]++]=b;
edge[b][top[b]++]=a;
} memset(dfsNum,0,sizeof(dfsNum));
dfsnum=0; tarjan(1,-1);
ans=solve(n);
printf("%d\n",ans);
}
return 0;
}
上面的代码的写法确实有问题,因为在同一个双连通分量中的点的low值并不一定相等,所以使用low值来判断是否在同一个分量中显然是有问题的。因此我们最安全的做饭时在tarjan的过程中,把桥标记出来,然后使用dfs跑每一个连通分量,最后使用桥统计每个点的度。 对于上面的问题,给大家带来的误导,非常抱歉。 下面的代码是对上面代码的修改,写的有点乱,供大家分享一下。
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <vector>
using namespace std;
const int Max=1010;
int top[Max],edge[Max][Max];//memset(top,0,sizeof(top));
int dfsNum[Max],dfsnum;//memset(dfsNum,0,sizeof(dfsNum)),dfsNum=1;
int low[Max];
int degree[Max];
int ans; bool exist[Max][Max]; void tarjan(int a,int fa)
{
dfsNum[a]=low[a]=++dfsnum;
for(int i=0;i<top[a];i++)
{
if(edge[a][i]!=fa)
{
if(dfsNum[edge[a][i]]==0)
{
tarjan(edge[a][i],a);
if(low[a]>low[edge[a][i]])
low[a]=low[edge[a][i]];
if(dfsNum[a] < low[edge[a][i]])
{
exist[a][edge[a][i]] = exist[edge[a][i]][a] = true;
}
}
else
{
if(low[a]>dfsNum[edge[a][i]])
low[a]=dfsNum[edge[a][i]];
}
}
}
} int cc[Max], ccCnt; void dfs(int fa, int u)
{
cc[u] = ccCnt;
for(int i=0; i<top[u]; i++)
{
int v = edge[u][i];
if(v != fa && !exist[u][v] && !cc[v])
{
// printf("v %d\n", v);
dfs(u, v);
}
}
} int solve(int n)
{
int i,j;
int a,b; memset(cc, 0, sizeof(cc));
ccCnt = 1;
for(i=1; i<=n; i++)
{
if(!cc[i])
{
dfs(-1, i);
ccCnt++;
}
} //cout<<"ccCnt "<<ccCnt<<endl; for(i=1;i<=n;i++)
{
a=i;
for(j=0;j<top[i];j++)
{
b=edge[a][j];
if(cc[a] != cc[b])
{
degree[cc[a]]++;
degree[cc[b]]++;
}
}
}
int leaves=0;
for(i=1;i<ccCnt;i++)
{
if(degree[i]==2)
{
leaves++;
}
}
return (leaves+1)/2;
} int main()
{
int n,m;
int i,a,b;
while(scanf("%d %d",&n,&m)!=EOF)
{
memset(top,0,sizeof(top));
memset(degree,0,sizeof(degree));
for(i=0;i<m;i++)
{
scanf("%d %d",&a,&b);
edge[a][top[a]++]=b;
edge[b][top[b]++]=a;
} memset(dfsNum,0,sizeof(dfsNum));
dfsnum=0; memset(exist, false, sizeof(exist)); tarjan(1,-1);
ans=solve(n);
printf("%d\n",ans);
}
return 0;
}

Tarjan算法求解桥和边双连通分量(附POJ 3352 Road Construction解题报告)的更多相关文章

  1. 【边双连通】poj 3352 Road Construction

    http://poj.org/problem?id=3352 [题意] 给定一个连通的无向图,求最少加多少条边使得这个图变成边双连通图 [AC] //#include<bits/stdc++.h ...

  2. POJ 3177 Redundant Paths POJ 3352 Road Construction(双连接)

    POJ 3177 Redundant Paths POJ 3352 Road Construction 题目链接 题意:两题一样的.一份代码能交.给定一个连通无向图,问加几条边能使得图变成一个双连通图 ...

  3. POJ 3352 Road Construction(边双连通分量,桥,tarjan)

    题解转自http://blog.csdn.net/lyy289065406/article/details/6762370   文中部分思路或定义模糊,重写的红色部分为修改过的. 大致题意: 某个企业 ...

  4. POJ 3352 Road Construction (边双连通分量)

    题目链接 题意 :有一个景点要修路,但是有些景点只有一条路可达,若是修路的话则有些景点就到不了,所以要临时搭一些路,以保证无论哪条路在修都能让游客到达任何一个景点 思路 :把景点看成点,路看成边,看要 ...

  5. POJ 3352 Road Construction(边—双连通分量)

    http://poj.org/problem?id=3352 题意: 给出一个图,求最少要加多少条边,能把该图变成边—双连通. 思路:双连通分量是没有桥的,dfs一遍,计算出每个结点的low值,如果相 ...

  6. POJ 3177 Redundant Paths & POJ 3352 Road Construction(双连通分量)

    Description In order to get from one of the F (1 <= F <= 5,000) grazing fields (which are numb ...

  7. poj 3352 Road Construction【边双连通求最少加多少条边使图双连通&&缩点】

    Road Construction Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 10141   Accepted: 503 ...

  8. POJ 3352 Road Construction 双联通分量 难度:1

    http://poj.org/problem?id=3352 有重边的话重边就不被包含在双连通里了 割点不一定连着割边,因为这个图不一定是点连通,所以可能出现反而多增加了双连通分量数的可能 必须要用割 ...

  9. poj 3352 Road Construction(边双连通分量+缩点)

    题目链接:http://poj.org/problem?id=3352 这题和poj 3177 一样,参考http://www.cnblogs.com/frog112111/p/3367039.htm ...

随机推荐

  1. luogu1006 传纸条

    题目大意 小渊坐在矩阵的左上角,坐标 (1,1 ),小轩坐在矩阵的右下角,坐标 (m,n) .从小渊传到小轩的纸条只可以向下或者向右传递,从小轩传给小渊的纸条只可以向上或者向左传递. 在活动进行中,小 ...

  2. C# Path 有关于文件路径等问题类(转)

    C# Path 标签:C#, Path C-Sharp  0 Path handles file path processing. The .NET Framework provides effect ...

  3. bzoj3033 太鼓达人——欧拉图搜索

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3033 考虑那 (1<<k) 个数,要形成答案,必然是相邻两个数间有 k-1 个重 ...

  4. springboot的登录拦截机制

    转自:https://blog.csdn.net/qq_26555463/article/details/78296103 如果是一个后台的管理项目的,有些东西是不能直接就可以访问的,必须要登录才可以 ...

  5. 0605-类的继承、重写、parent、final

    定义一个子类(man) //定义一个类 class renlei{ var $name = '王五'; var $age = ''; var $sex = ''; var $todo = ''; fu ...

  6. [Apple开发者帐户帮助]四、管理密钥(1)创建私钥以访问服务

    私钥允许您访问和验证与某些应用服务(如APN,MusicKit和DeviceCheck)的通信.您将在对该服务的请求中使用JSON Web令牌(JWT)中的私钥. 所需角色:帐户持有人或管理员. 在“ ...

  7. [Apple开发者帐户帮助]一、开始(2)登录您的开发者帐户

    使用Apple ID登录您的开发者帐户.如果您注册了付费程序(Apple Developer Program或Apple Developer Enterprise Program),请使用您用于注册的 ...

  8. spark作业运行过程之--DAGScheduler

    DAGScheduler--stage划分和创建以及stage的提交 本篇,我会从一次spark作业的运行为切入点,将spark运行过程中涉及到的各个步骤,包括DAG图的划分,任务集的创建,资源分配, ...

  9. U - Three displays

    Problem description It is the middle of 2018 and Maria Stepanovna, who lives outside Krasnokamensk ( ...

  10. Ubuntu1804安装Postgresql【转】

    转载自:https://huur.cn/course/yw/1591.html 关系数据库管理系统是许多网站和应用程序的关键组成部分.它们提供了一种结构化的方式来存储,组织和访问信息. Postgre ...