tarjan算法 POJ3177-Redundant Paths
参考资料传送门
http://blog.csdn.net/lyy289065406/article/details/6762370
http://blog.csdn.net/lyy289065406/article/details/6762432
http://blog.csdn.net/xinghongduo/article/details/6195337
题目链接
http://poj.org/problem?id=3177
题目大意:有F个牧场,1<=F<=5000,现在一个牧群经常需要从一个牧场迁移到另一个牧场。奶牛们已经厌烦老是走同一条路,所以有必要再新修几条路,这样它们从一个牧场迁移到另一个牧场时总是可以选择至少两条独立的路。现在F个牧场的任何两个牧场之间已经至少有一条路了,奶牛们需要至少有两条。
给定现有的R条直接连接两个牧场的路,F-1<=R<=10000,计算至少需要新修多少条直接连接两个牧场的路,使得任何两个牧场之间至少有两条独立的路。两条独立的路是指没有公共边的路,但可以经过同一个中间顶点
总结来说,给定一个连通的无向图G,至少要添加几条边,才能使其变为双连通图。
显然,当图G存在桥(割边)的时候,它必定不是双连通的。桥的两个端点必定分别属于图G的两个【边双连通分量】(注意不是点双连通分量),一旦删除了桥,这两个【边双连通分量】必定断开,图G就不连通了。但是如果在两个【边双连通分量】之间再添加一条边,桥就不再是桥了,这两个【边双连通分量】之间也就是双连通了。
有关图论的知识点的定义:http://www.byvoid.com/blog/biconnect/
要解这个问题,我们先要了解Tarjar算法
说到以Tarjan命名的算法,我们经常提到的有3个,其中就包括本文所介绍的求强连通分量的Tarjan算法。而提出此算法的普林斯顿大学的Robert E Tarjan教授也是1986年的图灵奖获得者。
首先明确几个概念。
1. 强连通图。在一个强连通图中,任意两个点都通过一定路径互相连通。比如图一是一个强连通图,而图二不是。因为没有一条路使得点4到达点1、2或3。
2. 强连通分量。在一个非强连通图中极大的强连通子图就是该图的强连通分量。比如图三中子图{1,2,3,5}是一个强连通分量,子图{4}是一个强连通分量。
其实,tarjan算法的基础是DFS。我们准备两个数组Low和Dfn。Low数组是一个标记数组,记录该点所在的强连通子图所在搜索子树的根节点的Dfn值(很绕嘴,往下看你就会明白),Dfn数组记录搜索到该点的时间,也就是第几个搜索这个点的。根据以下几条规则,经过搜索遍历该图(无需回溯)和对栈的操作,我们就可以得到该有向图的强连通分量。
- 数组的初始化:当首次搜索到点p时,Dfn与Low数组的值都为搜索到该点的次序。(比如1这点是第一个被搜索的,则Dfn[1] = 1, Low[1] = 1)
- 堆栈:每搜索到一个点,将它压入栈顶。
- 当点p有到点p’的路径时,如果p’不在栈中,且p’没有进入过栈,则将p’压入栈顶,Dfn[p’]与Low[p’]数组的值都为搜索到该点的次序 ,并递归搜索p’点。
- 当点p有到点p’的路径时,如果p’不在栈中,且p’已经出栈,则忽略。
- 当点p有到点p’的路径时,如果p’在栈中,p的low值为p的low值和p’的low值中较小的一个。
- 当点p的所有子树已经全部遍历过后,如果p的low值等于dfn值,则说明p并没有返祖路径,以p为根构成一个强连通分量, 将p以及在它之上的元素弹出栈。这些出栈的元素组成一个强连通分量。
- 返回p的父亲节点继续搜索(或许会更换搜索的起点,因为整个有向图可能分为两个不连通的部分),直到所有点被遍历。
由于每个顶点只访问过一次,每条边也只访问过一次,我们就可以在O(n+m)的时间内求出有向图的强连通分量。但是,这么做的原理是什么呢?
Tarjan算法的操作原理如下:
- Tarjan算法基于定理:在任何深度优先搜索中,同一强连通分量内的所有顶点均在同一棵深度优先搜索树中。也就是说,强连通分量一定是有向图的某个深搜树子树。
- 可以证明,当一个点既是强连通子图Ⅰ中的点,又是强连通子图Ⅱ中的点,则它是强连通子图Ⅰ∪Ⅱ中的点。
- 这样,我们用low值记录该点所在强连通子图对应的搜索子树的根节点的Dfn值。注意,该子树中的元素在栈中一定是相邻的,且根节点在栈中一定位于所有子树元素的最下方。
- 强连通分量是由若干个环组成的。所以,当有环形成时(也就是搜索的下一个点已在栈中),我们将这一条路径的low值统一,即这条路径上的点属于同一个强连通分量。
- 如果遍历完整个搜索树后某个点的dfn值等于low值,则它是该搜索子树的根。这时,它以上(包括它自己)一直到栈顶的所有元素组成一个强连通分量。
红字部分需要在编程中注意,当递归返回上一层时,一定要检查当前节点和栈顶节点的low值,如果当前节点大于栈顶节点,则把栈顶节点的low值赋给当前节点。
OK,了解了Tarjan算法,再回到本问题,给定一个连通的无向图G,至少要添加几条边,才能使其变为双连通图。
Targan原本求的是有向图的强连通分量,这里我们变换一下思想,如果把一个有向图的强连通分量的每条边都变成无向边,则这个强连通分量就是一个双连通图,即任何一点都有到其他点的两条路径。
所以我们要做的就是将原本的无向图变成双向连接的有向图,用邻接矩阵很容易实现,然后在图中用Tarjan算法求出所有的强连通分量,在这里其实就是双连通子图。
求出所有的双连通子图后,我们要做的就是缩点,其实并没必要把点都合到一起,只需要找个数组表示一下, 在一个子图里面的点编号相同就可以, 参考下图。
其中Low[4]=Low[9]=Low[10]
Low[3]=Low[7]=Low[8]
Low[2]=Low[5]=Low[6]
Low[1]独自为政....
把Low值相同的点划分为一类,每一类就是一个【边双连通分量】,也就是【缩点】了,不难发现,连接【缩点】之间的边,都是图G的桥,那么我们就得到了上右图以缩点为结点,已桥为树边所构造成的树。
问题再次被转化为“至少在缩点树上增加多少条树边,使得这棵树变为一个双连通图”。
首先知道一条等式:
若要使得任意一棵树,在增加若干条边后,变成一个双连通图,那么
至少增加的边数 =( 这棵树总度数为1的结点数 + 1 )/ 2
(证明就不证明了,自己画几棵树比划一下就知道了)
那么我们只需求缩点树中总度数为1的结点数(即叶子数)有多少就可以了。换而言之,我们只需求出所有缩点的度数,然后判断度数为1的缩点有几个,问题就解决了。
求出所有缩点的度数的方法
两两枚举图G的直接连通的点,只要这两个点不在同一个【缩点】中,那么它们各自所在的【缩点】的度数都+1。注意由于图G是无向图,这样做会使得所有【缩点】的度数都是真实度数的2倍,必须除2后再判断叶子。
好了,上代码:
#include <cstdio>
#include <iostream>
#include <memory.h>
#include <stack>
#include <set>
#include <cstdlib>
#include <climits>
#include <vector> using namespace std;
//F 农场数, R 路径数, point1路径起点, point2路径终点
int F, R, point1, point2;
//计算遍历次序
int count;
//每个点可达到的最早被遍历的点的次序
int low[];
//每个点被遍历到的次序
int dfn[];
//每个点所在的缩点组
int group[];
//每个点是否在栈中
bool inStack[];
//图的邻接矩阵
bool graph[][];
//栈
stack<int> pointStack;
//缩点编号
int nodeID;
//缩点数
int nodeCount; void tarjan(int src, int father)
{
for(int i=; i<=F; ++i)
{
//去掉父节点的影响
if(i!=father && graph[src][i] == )
{
if(inStack[i])
{
low[src] = min(low[src],low[i]);
}
//already pop
else if(dfn[i]!=)
{
continue;
}
else
{
pointStack.push(i);
inStack[i] = true;
low[i] = ++count;
dfn[i] = count;
tarjan(i, src);
} }
}
//当前点的子节点都遍历过以后,一定要比较一下栈顶low值
low[src] = low[pointStack.top()]; if(low[src] == dfn[src])
{
//merge point
int tmp;
do
{
tmp = pointStack.top();
pointStack.pop();
inStack[tmp] = false;
group[tmp] = nodeID;
}
while(tmp != src);
nodeID++;
nodeCount++; }
} int getMinPath()
{
//统计每个缩点的边数
int *count = new int[nodeCount+]();
for(int i=; i<=F; ++i)
{
for(int j=; j<=F; ++j)
{
//遍历所有的边,如果边的两个节点不在一个缩点组里面
if(graph[i][j] == true && group[i] != group[j])
{
++count[group[i]];
++count[group[j]];
}
}
} int ret = ;
for(int i=; i<=nodeCount; ++i)
{
if(count[i]/ == )
++ret;
}
return (ret+)/;
} int main(int argc, char** argv)
{
while (scanf("%d %d", &F, &R) != EOF)
{
memset(low,,sizeof(low));
memset(dfn,,sizeof(dfn));
memset(group,,sizeof(group));
memset(inStack,false,sizeof(inStack));
memset(graph,false,sizeof(graph));
nodeCount = ; for(int i=; i<R; ++i)
{
cin >> point1 >> point2;
graph[point1][point2] = ;
graph[point2][point1] = ;
}
//将第一个点加入栈中
pointStack.push();
inStack[] = true;
low[] = ;
dfn[] = ;
count = ;
nodeID = ; tarjan(,-); cout << getMinPath() << endl; } return ;
}
tarjan算法 POJ3177-Redundant Paths的更多相关文章
- POJ3177 Redundant Paths —— 边双联通分量 + 缩点
题目链接:http://poj.org/problem?id=3177 Redundant Paths Time Limit: 1000MS Memory Limit: 65536K Total ...
- POJ3177:Redundant Paths(并查集+桥)
Redundant Paths Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 19316 Accepted: 8003 ...
- [POJ3177]Redundant Paths(双联通)
在看了春晚小彩旗的E技能(旋转)后就一直在lol……额抽点时间撸一题吧…… Redundant Paths Time Limit: 1000MS Memory Limit: 65536K Tota ...
- POJ3177 Redundant Paths 双连通分量
Redundant Paths Description In order to get from one of the F (1 <= F <= 5,000) grazing fields ...
- POJ3177 Redundant Paths【tarjan边双联通分量】
LINK 题目大意 给你一个有重边的无向图图,问你最少连接多少条边可以使得整个图双联通 思路 就是个边双的模板 注意判重边的时候只对父亲节点需要考虑 你就dfs的时候记录一下出现了多少条连向父亲的边就 ...
- poj3177 Redundant Paths
Description In order to get from one of the F (1 <= F <= 5,000) grazing fields (which are numb ...
- POJ3177 Redundant Paths(边双连通分量+缩点)
题目大概是给一个无向连通图,问最少加几条边,使图的任意两点都至少有两条边不重复路径. 如果一个图是边双连通图,即不存在割边,那么任何两个点都满足至少有两条边不重复路径,因为假设有重复边那这条边一定就是 ...
- [POJ3177]Redundant Paths(双连通图,割边,桥,重边)
题目链接:http://poj.org/problem?id=3177 和上一题一样,只是有重边. 如何解决重边的问题? 1. 构造图G时把重边也考虑进来,然后在划分边双连通分量时先把桥删去,再划分 ...
- POJ3177 Redundant Paths【双连通分量】
题意: 有F个牧场,1<=F<=5000,现在一个牧群经常需要从一个牧场迁移到另一个牧场.奶牛们已经厌烦老是走同一条路,所以有必要再新修几条路,这样它们从一个牧场迁移到另一个牧场时总是可以 ...
- poj3352 Road Construction & poj3177 Redundant Paths (边双连通分量)题解
题意:有n个点,m条路,问你最少加几条边,让整个图变成边双连通分量. 思路:缩点后变成一颗树,最少加边 = (度为1的点 + 1)/ 2.3177有重边,如果出现重边,用并查集合并两个端点所在的缩点后 ...
随机推荐
- ASP.NET Web API与Rest web api(一)
本文档内容大部分来源于:http://www.cnblogs.com/madyina/p/3381256.html HTTP is not just for serving up web pages. ...
- hilbert矩阵
希尔伯特矩阵 希尔伯特矩阵是一种数学变换矩阵 Hilbert matrix,矩阵的一种,其元素A(i,j)=1/(i+j-1),i,j分别为其行标和列标. 即: [1,1/2,1/3,……,1/n] ...
- tiny4412SD启动盘的制作--1
一.使用SD-flasher工具烧写superboot到SD卡. 1.SD-Flasher.exe 会对 SD 卡进行分区,第一个分区为 130M 用于存放 Superboot4412, 剩下的空间格 ...
- leetcode 96 Unique Binary Search Trees ----- java
Given n, how many structurally unique BST's (binary search trees) that store values 1...n? For examp ...
- Quailty and Binary Operation
Quailty and Binary Operation 题意 分别给\(N,M(N,M \le 50000)\)两个数组\(A\)和\(B\),满足\(0 \le A_i,B_i \le 50000 ...
- jQuery中过滤选择器first和first-child的区别
:first过滤器只匹配第一个子元素,而:first-child过滤器将为每个父元素匹配个子元素. 对于下面html代码: <ul> <li>John</li> & ...
- ExtJS控件样式修改及美化
Extjs项目对富客户端开发提供了强有力的支持,甚至改变了前端的开发方式,使得开发变得更加趋向于“面向组件”.对界面的美化而言,也是根本性的改变.普通的网页美工面对extjs项目根本无法下手,需要脚本 ...
- 1029c语言文法定义与c程序的推导过程
program → external_declaration | program external_declaration <源程序>→ <外部声明> | <源程序> ...
- apache性能优化
perfork进程数 http://sookk8.blog.51cto.com/455855/275759/ mod_cache 磁盘缓存 http://www.cnblogs.com/fnng/ar ...
- freeswitch 1.4
yum install git gcc-c++ autoconf automake libtool wget python ncurses-devel zlib-devel libjpeg-devel ...