题解转自http://blog.csdn.net/lyy289065406/article/details/6762370 文中部分思路或定义模糊,重写的红色部分为修改过的。
大致题意:
某个企业想把一个热带天堂岛变成旅游胜地,岛上有N个旅游景点,保证任意2个旅游景点之间有路径连通的(可间接连通)。而为了给游客提供更方便的服务,该企业要求道路部门在某些道路增加一些设施。
道路部门每次只会选择一条道路施工,在该条道路施工完毕前,其他道路依然可以通行。然而有道路部门正在施工的道路,在施工完毕前是禁止游客通行的。这就导致了在施工期间游客可能无法到达一些景点。
为了在施工期间所有旅游景点依然能够正常对游客开放,该企业决定搭建一些临时桥梁,使得不管道路部门选在哪条路进行施工,游客都能够到达所有旅游景点。给出当下允许通行的R条道路,问该企业至少再搭建几条临时桥梁,才能使得游客无视道路部门的存在到达所有旅游景点?
题目所给的图是连通的,且所给的边没有重复的,所以不考虑重边。
注意:不要把Sample Input 1读入,更不要把它输出!!!!!
首先建立模型:
给定一个连通的无向图G,至少要添加几条边,才能使其变为边-双连通图(因为在修道路是一条边!)。
模型很简单,正在施工的道路我们可以认为那条边被删除了。那么一个图G能够在删除任意一条边后,仍然是连通的,当且仅当图G至少为边双连通的。
PS:不要问我为什么不是3-连通、4-连通...人家题目问“至少添加几条边”好不...
显然,当图G存在桥(割边)的时候,它必定不是双连通的。桥的两个端点必定分别属于图G的两个【边双连通分量】(注意不是点双连通分量),一旦删除了桥,这两个【边双连通分量】必定断开,图G就不连通了。但是如果在两个【边双连通分量】之间再添加一条边,桥就不再是桥了,这两个【边双连通分量】之间也就是边双连通了。
那么如果图G有多个【边双连通分量】呢?至少应该添加多少条边,才能使得任意两个【边双连通分量】之间都是边双连通的(也就是图G是边双连通的)?
这个问题就是本题的问题。要解决这个问题:
1、 首先要找出图G的所有【边双连通分量】。
Tarjan算法用来寻找图G的所有【边双连通分量】是最简单有效的方法,因为Tarjan算法在DFS过程中会对图G所有的结点都生成一个Low值,而由于题目已表明任意两个结点之间不会出现重边,因此Low值相同的两个结点必定在同一个【边双连通分量】中(但是!!low值不同的点并不代表就不在同一个边双连通分量中,因为由于先后访问的顺序的原因,low值可能会不同,这跟并查集的道理是一样的,如果不进行压缩路径,几乎很多人的直接上级都不会是该团队的boss,这点可以模仿并查集的方式将其归到只有两层,查起来就很快了,否则可能超时!!当然你不考虑这点也能AC,水果了那些简单数据,但是下面这2组数据你可能过不了)!
11 14
1 2
1 3
1 4
2 5
6 11
2 6
5 6
5 11
3 7
3 8
7 8
4 9
4 10
9 10
11 14
1 2
1 3
1 4
5 11
2 5
2 6
5 6
6 11
3 7
3 8
7 8
4 9
4 10
9 10
以上两个测试数据的ans都是2。
2、 把每一个【边双连通分量】都看做一个点(即【缩点】)
也有人称【缩点】为【块】,都是一样的。其实缩点不是真的缩点,只要利用Low值对图G的点分类处理,就已经缩点了。
以样例1为例,样例1得到的图G为上左图,
其中Low[4]=Low[9]=Low[10]
Low[3]=Low[7]=Low[8]
Low[2]=Low[5]=Low[6]
Low[1]独自为政....
把Low值相同的点划分为一类,每一类就是一个【边双连通分量】,也就是【缩点】了,不难发现,连接【缩点】之间的边,都是图G的桥,那么我们就得到了上右图以缩点为结点,已桥为树边所构造成的树。
以上这样以low值判断边连通分量是没有错的!只是要确保low值能区分出该块,当然,你可以用栈,就像求点双连通分量那样(并不是完全一样,那是以割点来区分的,这是以桥来区分的)。
3、 问题再次被转化为“至少在缩点树上增加多少条树边,使得这棵树变为一个边双连通图”。
首先知道一条等式:
若要使得任意一棵树,在增加若干条边后,变成一个双连通图,那么
至少增加的边数 =( 这棵树总度数为1的结点数 + 1 )/ 2
(证明就不证明了,自己画几棵树比划一下就知道了)
那么我们只需求缩点树中总度数为1的结点数(不一定是叶子数,如果树根仅有1个孩子,同样要将其统计进来)有多少就可以了。换而言之,我们只需求出所有缩点的度数,然后判断度数为1的缩点有几个,问题就解决了。
4、 求出所有缩点的度数的方法
两两枚举图G的直接连通的点(如果求出了桥,那么枚举桥也行的,相同于直接枚举树的边),只要这两个点不在同一个【缩点】中,那么它们各自所在的【缩点】的度数都+1。注意由于图G时无向图,这样做会使得所有【缩点】的度数都是真实度数的2倍,必须除2后再判断是否度为1。
1 #include <iostream> 2 #include <cmath> 3 #include <cstdio> 4 #include <vector> 5 #include <cstring> 6 #include <set> 7 //#include <bits/stdc++.h> 8 using namespace std; 9 const int N=1000+5; 10 vector<int> vect[N]; 11 int low[N], dfn[N], cnter; 12 int du[N]; 13 int pre[N];//这个与dfn是相反的索引 14 vector<pair<int,int> > cutt; 15 int find(int x) //寻找x的low值 16 { 17 if(low[x]==dfn[x]) return low[x]; 18 return low[x]=find( pre[low[x] ] ); 19 } 20 21 void DFS(int x, int far) 22 { 23 low[x]= dfn[x]= ++cnter; 24 pre[cnter]=x; //标记第cnter个访问的是谁 25 for(int i=0; i<vect[x].size(); i++) 26 { 27 int t=vect[x][i]; 28 if(!dfn[t]) 29 { 30 DFS(t,x); 31 low[x]=min(low[x],low[t]); 32 if(low[t]>dfn[x]) cutt.push_back(make_pair(x,t));//桥(即树边) 33 } 34 else if(t!=far) low[x]=min(low[x],dfn[t]); 35 } 36 } 37 38 int cal_bcc(int f) //计算边双连通分量 39 { 40 cutt.clear(); 41 memset(du,0,sizeof(du)); 42 memset(low,0,sizeof(low)); 43 memset(dfn,0,sizeof(dfn)); 44 memset(pre,0,sizeof(pre)); 45 cnter=0; 46 DFS(1,0); 47 for(int i=0; i<cutt.size(); i++) 48 { 49 int a=cutt[i].first; 50 int b=cutt[i].second; 51 du[find(a)]++; 52 du[find(b)]++; 53 } 54 55 int ans=0; 56 for(int i=1; i<=f; i++) if(du[i]==1) ans++; 57 return ((ans+1)/2); 58 } 59 60 int main() 61 { 62 //freopen("input.txt", "r", stdin); 63 int f, r, a, b, j=0; 64 char s[N]; 65 while(cin>>f>>r) 66 { 67 for(int i=1; i<=f; i++) vect[i].clear(); 68 while(r--) 69 { 70 scanf("%d%d", &a, &b); 71 vect[a].push_back(b); 72 vect[b].push_back(a); 73 } 74 printf("%d\n",cal_bcc(f)); 75 } 76 return 0; 77 }
枚举树边的AC代码
1 #include <iostream> 2 #include <cmath> 3 #include <cstdio> 4 #include <vector> 5 #include <cstring> 6 #include <set> 7 //#include <bits/stdc++.h> 8 using namespace std; 9 const int N=1000+5; 10 vector<int> vect[N]; 11 int low[N], dfn[N], cnter; 12 int du[N]; 13 int pre[N];//与dfn相反的索引 14 int find(int x) //寻找x的low值,顺便压缩路径 15 { 16 if(low[x]==dfn[x]) return low[x]; 17 return low[x]=find( pre[low[x] ] ); 18 } 19 20 void DFS(int x, int far) 21 { 22 low[x]= dfn[x]= ++cnter; 23 pre[cnter]=x; //第几个访问的是谁 24 for(int i=0; i<vect[x].size(); i++) 25 { 26 int t=vect[x][i]; 27 if(!dfn[t]) 28 { 29 DFS(t,x); 30 low[x]=min(low[x],low[t]); 31 } 32 else if(t!=far) low[x]=min(low[x],dfn[t]); 33 } 34 35 } 36 37 int cal_bcc(int f) //找割点及双连通分量 38 { 39 cutt.clear(); 40 memset(du,0,sizeof(du)); 41 memset(low,0,sizeof(low)); 42 memset(dfn,0,sizeof(dfn)); 43 memset(pre,0,sizeof(pre)); 44 cnter=0; 45 DFS(1,0); 46 47 for(int i=1; i<=f; i++) 48 for(int j=0; j<vect[i].size(); j++) 49 { 50 int b=vect[i][j]; 51 if(find(i)!=find(b)) du[low[i]]++; 52 } 53 54 int ans=0; 55 for(int i=1; i<=f; i++) if(du[i]==1) ans++; 56 return ((ans+1)/2); 57 } 58 59 int main() 60 { 61 freopen("input.txt", "r", stdin); 62 int f, r, a, b, j=0; 63 char s[N]; 64 while(cin>>f>>r) 65 { 66 for(int i=1; i<=f; i++) vect[i].clear(); 67 while(r--) 68 { 69 scanf("%d%d", &a, &b); 70 vect[a].push_back(b); 71 vect[b].push_back(a); 72 } 73 printf("%d\n",cal_bcc(f)); 74 } 75 return 0; 76 }
枚举每条边的AC代码