Road Construction

来自这里

双连通分量

题意:比较裸的题意,就是给一个无向图,问添加多少条边后能使整个图变成双连通分量

分析:建议先学了双连通分量的相关知识,因为这题是算是个模板题(我自己写了模板,过了这题,但是还没有充分测试),如果没学好相关知识即便这个模板题也不好懂

双连通分量分为【点双连通分量,边双连通分量】,这题是个边双连通分量,就是要求出整个图的边双连通分量,然后缩点,然后找出缩点后每个点的度,度为1的点其实是树叶,答案就是(leaf+1)/2去上整,为什么是这个答案,网上的解释是,每次找到两个叶子他们的最近公共祖先最远,然后给这两个叶子连一条边,然后依次找出这样的点,所以是(leaf+1)/2

现在为问题就是1.怎么缩点。2.怎么统计缩点后的度

 

 ////////////  注意一点  ///////////////////

这题的题意是保证了图是连通的,所以才有上面的计算公式,所以只要dfs一次,如果图不连通,可能要dfs多次,并且不是上面的计算公式(leaf+1)/2

 

两种做法:

1.简化tarjan,在边双连通分量中,每个点low[u]其实已经记录是这个点u是属于哪个边双连通分量了,low[u] = low[v] ,那么点u和点v在一个边双连通分量中,所以我们可以不用急着找什么连通分量,我们先运行一次dfs,把那个顶点的low都计算出来,然后我们查看原图的每一条边(u,v),看看原图的两个点u,v是不是属于不同的连通分量,是的话,缩点后它们之间就有一条边,那么就要统计它们的度。统计完后就可以知道哪些是叶子了

2.上面的做法,是在dfs后再处理边双连通分量的,可以一边dfs,一边就找到边双连通分量呢?是可以,这个方法才是要讲的重点

首先有几个知识点

先搞清楚,树边,后向边,前向边,横叉边是什么,维基百科有讲解

对原图缩点后,变成了一棵无根树,树的边是什么?其实就是原图的桥,所以,我们可以在dfs过程中把所有的桥保存下来,放在一个表中,然后dfs完后,直接去查看那个表,桥的两端是两个点,这两个点是一个属于两个不同的边双连通分量的,所以我们可以直接统计这些缩点的度

判断桥的条件比较简单,对于一条树边(注意是树边),在dfs过程中是从u到v的(可以看做u是v的父亲),且满足low[v] > dfn[u] , 那么无向边(u,v)就是桥,就可以把这条边保存在表中

另外在这个dfs中借助了栈(方法1可以用栈,也可以不用,因为方法1简化了tarjan),在dfs过程中访问了点就不断入栈。在找到一条桥后,就准备将一些点出栈,因为这些准备出栈的点都是属于一个边双连通分量的,出栈的终于条件是,点v最后出来,点u不能出,注意,点u不能,点u不是属于点v的那个连通分量的,因为桥(u,v)分开了他们

 

方法1:简化了tarjan的过程,注意vis的意思,其实它的作用代替了栈的作用,vis[i]=0,1,2分别表示还没访问,已经访问但是还没退出,访问完并退出,它表示的是一种时间上的顺序

这个代码跑得快,0ms

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstring>
 4 using namespace std;
 5 #define N 1010
 6 #define M 1010
 7 #define INF 0x3f3f3f3f
 8 
 9 int n,tot;
10 int head[N];
11 struct edge
12 {
13     int u,v,next;
14 }e[2*M];
15 int dfn[N],low[N],vis[N],dcnt,bcnt,de[N];
16 
17 inline int min(int x , int y)
18 {
19     return x<y ? x:y;
20 }
21 
22 void add(int u ,int v ,int k)
23 {
24     e[k].u = u; e[k].v = v;
25     e[k].next = head[u]; head[u] = k++;
26     u = u^v; v = u^v; u = u^v;
27     e[k].u = u; e[k].v = v;
28     e[k].next = head[u]; head[u] = k++;
29 }
30 
31 void dfs(int u ,int fa)
32 {
33     dfn[u] = low[u] = ++dcnt;
34     vis[u] = 1;
35     for(int k=head[u]; k!=-1; k=e[k].next)
36     {
37         int v = e[k].v;
38         if(v == fa) continue;
39         if(!vis[v]) //树边
40         {
41             dfs(v,u);
42             low[u] = min(low[u] , low[v]);
43         }
44         else if(vis[v] == 1) //后向边
45             low[u] = min(low[u] , dfn[v]);
46         //如果是横叉边为vis[v] == 2 , 跳过
47     }
48     vis[u] = 2;
49 }
50 
51 void solve()
52 {
53     memset(dfn,0,sizeof(dfn));
54     memset(de,0,sizeof(de));
55     memset(vis,0,sizeof(vis));
56     dcnt = bcnt = 0;
57     for(int i=1; i<=n; i++)
58         if(!vis[i])
59             dfs(i,i);
60     for(int u=1; u<=n; u++)
61         for(int k=head[u]; k!=-1; k=e[k].next)
62         {
63             int v = e[k].v;
64             if(low[u] != low[v]) //属于不同的边连通分量
65                 de[low[u]]++;
66         }
67     int leaf = 0;
68     for(int i=1; i<=n; i++)
69         if(de[i] == 1)
70             leaf++;
71     cout << (leaf+1)/2 << endl;
72 }
73 
74 int main()
75 {
76     while(cin>> n >> tot)
77     {
78         int u,v,k = 0;
79         memset(head,-1,sizeof(head));
80         for(int i=0; i<tot; i++,k+=2)
81         {
82             cin >> u >> v;
83             add(u,v,k);
84         }
85         solve();
86     }
87     return 0;
88 }
View Code

相关文章:

  • 2021-10-04
  • 2021-06-25
  • 2022-12-23
  • 2021-10-16
  • 2021-08-04
猜你喜欢
  • 2021-07-15
  • 2021-11-17
  • 2022-01-20
  • 2021-12-06
  • 2021-08-06
  • 2021-11-27
相关资源
相似解决方案