构造双连通图:一个有桥的连通图,如何把它通过加边变成边双连通图

一个有桥的连通图,如何把它通过加边变成边双连通图?方法为首先求出所有的桥,然后删除这些桥边,剩下的每个连通块都是一个双连通子图。把每个双连通子图收缩为一个顶点,再把桥边加回来,最后的这个图一定是一棵树,边连通度为1。

统计出树中度为1的节点的个数,即为叶节点的个数,记为leaf。则至少在树上添加(leaf+1)/2条边,就能使树达到边二连通,所以至少添加的边数就是(leaf+1)/2。具体方法为,首先把两个最近公共祖先最远的两个叶节点之间连接一条边,这样可以把这两个点到祖先的路径上所有点收缩到一起,因为一个形成的环一定是双连通的。然后再找两个最近公共祖先最远的两个叶节点,这样一对一对找完,恰好是(leaf+1)/2次,把所有点收缩到了一起。

题目: http://poj.org/problem?id=3352

题意:给你一个连通的无向图,现在问你最少在该图中添加几条边,能使得该图变成边双连通图?

我没有搞懂为什么至少在树上添加(leaf+1)/2条边,就能使树达到边二连通,暂且记住吧。

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <stack>
#define N 10010
using namespace std;
struct node
{
int x,y,w,next,flag;
} eg[*N];
stack<int>q;
int tt,head[N],dfn[N],low[N],ti,n,m,be[N],bridge[N][],tp;
int cnt,de[N];
void init()
{
tt=;
ti=;
tp=;
cnt=;
memset(head,-,sizeof(head));
memset(dfn,,sizeof(dfn));
memset(be,,sizeof(be));
memset(de,,sizeof(de));
memset(bridge,,sizeof(bridge));
while(!q.empty()) q.pop();
}
void add(int xx,int yy)
{
eg[tt].x=xx;
eg[tt].y=yy;
eg[tt].next=head[xx];
head[xx]=tt++;
}
void tarjan(int u,int fa)
{
dfn[u]=low[u]=++ti;
q.push(u);
for(int i=head[u]; i!=-; i=eg[i].next)
{
int v=eg[i].y;
if(v==fa) continue;
if(!dfn[v])
{
tarjan(v,u);
low[u]=min(low[u],low[v]);
if(low[v]>dfn[u])//边(u,v)为桥,可以统计一个边连通分支
{
bridge[tp][]=u;
bridge[tp++][]=v;
++cnt;
int w;
do
{
w=q.top();
q.pop();
be[w]=cnt;
}
while(w!=v);//注意点u并没有出栈,因为点u属于另一个边连通分量
}
}
else
{
low[u]=min(dfn[v],low[u]);
}
}
}
int main()
{
int xx,yy;
while(scanf("%d%d",&n,&m)!=EOF)
{
init();
for(int i=; i<=m; i++)
{
scanf("%d%d",&xx,&yy);
add(xx,yy);
add(yy,xx);
}
tarjan(,-);
if(!q.empty())
{
++cnt;
int w;
do
{
w=q.top();
q.pop();
be[w]=cnt;
}
while(w!=);
}
for(int i=; i<tp; i++)
{
int u=bridge[i][];
int v=bridge[i][];
de[be[u]]++;
de[be[v]]++; //统计缩点后的的度
}
/*for(int i=0;i<tp;i++)
{
printf("bridge==%d %d\n",bridge[i][0],bridge[i][1]);
}
printf("cnt==%d\n",cnt);*/
int leaf=;
for(int i=; i<=cnt; i++)
{
if(de[i]==)
leaf++;
}
printf("%d\n",(leaf+)/);
}
return ;
}

第一次交的,需要用数组f[N]防止跨边,我感觉实际上这样是没用的,但大家代码都这么写我就贴一下吧,无向图是没有跨边的。

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <stack>
#define N 10010
using namespace std;
struct node
{
int x,y,w,next,flag;
} eg[*N];
stack<int>q;
int tt,head[N],dfn[N],low[N],ti,n,m,be[N],bridge[N][],tp;
int cnt,de[N],f[N];
void init()
{
tt=;
ti=;
tp=;
cnt=;
memset(head,-,sizeof(head));
memset(dfn,,sizeof(dfn));
memset(be,,sizeof(be));
memset(de,,sizeof(de));
memset(bridge,,sizeof(bridge));
memset(f,,sizeof(f));
while(!q.empty()) q.pop();
}
void add(int xx,int yy)
{
eg[tt].x=xx;
eg[tt].y=yy;
eg[tt].next=head[xx];
head[xx]=tt++;
}
void tarjan(int u,int fa)
{
dfn[u]=low[u]=++ti;
q.push(u);
f[u]=;
for(int i=head[u]; i!=-; i=eg[i].next)
{
int v=eg[i].y;
if(v==fa) continue;
if(!dfn[v])
{
tarjan(v,u);
low[u]=min(low[u],low[v]);
if(low[v]>dfn[u])//边(u,v)为桥,可以统计一个边连通分支
{
bridge[tp][]=u;
bridge[tp++][]=v;
++cnt;
int w;
do
{
w=q.top();
q.pop();
f[w]=;
be[w]=cnt;
}
while(w!=v);//注意点u并没有出栈,因为点u属于另一个边连通分量
}
}
else if(f[v])
{
low[u]=min(dfn[v],low[u]);
}
}
}
int main()
{
int xx,yy;
while(scanf("%d%d",&n,&m)!=EOF)
{
init();
for(int i=; i<=m; i++)
{
scanf("%d%d",&xx,&yy);
add(xx,yy);
add(yy,xx);
}
tarjan(,-);
if(!q.empty())
{
++cnt;
int w;
do
{
w=q.top();
q.pop();
be[w]=cnt;
}
while(w!=);
}
for(int i=;i<tp;i++)
{
int u=bridge[i][];
int v=bridge[i][];
de[be[u]]++;
de[be[v]]++; //统计缩点后的的度
}
/*for(int i=0;i<tp;i++)
{
printf("bridge==%d %d\n",bridge[i][0],bridge[i][1]);
}
printf("cnt==%d\n",cnt);*/
int leaf=;
for(int i=;i<=cnt;i++)
{
if(de[i]==)
leaf++;
}
printf("%d\n",(leaf+)/);
}
return ;
}

sdut2506:http://acm.sdut.edu.cn/sdutoj/problem.php?action=showproblem&problemid=2506

题目解析:不是很确定自己是不是做对了,只能说数据很水吧,他丫的题目说给的是连通图,第一个事例就是不联通的,然后我发现在度为0的连通分量上添加一条边

到任意连通分量不会改变原先连通分量度为1的个数,还是能利用(leaf+1)/2这一结论。

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <stack>
#define N 10010
using namespace std;
struct node
{
int x,y,w,next,flag;
} eg[*N];
stack<int>q;
int tt,head[N],dfn[N],low[N],ti,n,m,be[N],bridge[N][],tp;
int cnt,de[N];
void init()
{
tt=;
ti=;
tp=;
cnt=;
memset(head,-,sizeof(head));
memset(dfn,,sizeof(dfn));
memset(be,,sizeof(be));
memset(de,,sizeof(de));
memset(bridge,,sizeof(bridge));
while(!q.empty()) q.pop();
}
void add(int xx,int yy)
{
eg[tt].x=xx;
eg[tt].y=yy;
eg[tt].next=head[xx];
head[xx]=tt++;
}
void tarjan(int u,int fa)
{
dfn[u]=low[u]=++ti;
q.push(u);
for(int i=head[u]; i!=-; i=eg[i].next)
{
int v=eg[i].y;
if(v==fa) continue;
if(!dfn[v])
{
tarjan(v,u);
low[u]=min(low[u],low[v]);
if(low[v]>dfn[u])//边(u,v)为桥,可以统计一个边连通分支
{
bridge[tp][]=u;
bridge[tp++][]=v;
++cnt;
int w;
do
{
w=q.top();
q.pop();
be[w]=cnt;
}
while(w!=v);//注意点u并没有出栈,因为点u属于另一个边连通分量
}
}
else
{
low[u]=min(dfn[v],low[u]);
}
}
}
int main()
{
int xx[],yy[];
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&m);
init();
for(int i=; i<m; i++)
{
scanf("%d%d",&xx[i],&yy[i]);
add(xx[i],yy[i]);
add(yy[i],xx[i]);
}
for(int i=; i<=n; i++)
{
if(!dfn[i])
{
tarjan(i,-);
if(!q.empty())
{
++cnt;
int w;
do
{
w=q.top();
q.pop();
be[w]=cnt;
}
while(w!=i);
}
}
}
for(int i=; i<tp; i++)
{
int u=bridge[i][];
int v=bridge[i][];
de[be[u]]++;
de[be[v]]++; //统计缩点后的的度
}
int du[N];
int sum=;
memset(du,,sizeof(du));
for(int i=; i<m; i++)
{
if(be[xx[i]]!=be[yy[i]])
{
du[be[xx[i]]]++;
du[be[yy[i]]]++;
}
}
if(cnt==)
{
printf("0\n");
continue;
}
for(int i=; i<=cnt; i++)
{
if(du[i]==)
sum++;
}
// printf("sum==%d\n",sum);
/* for(int i=0;i<tp;i++)
{
printf("bridge==%d %d\n",bridge[i][0],bridge[i][1]);
}
printf("cnt==%d\n",cnt);*/
int leaf=;
for(int i=; i<=cnt; i++)
{
if(de[i]==)
leaf++;
}
// printf("leaf==%d\n",leaf);
printf("%d\n",(leaf+)/+sum);
}
return ;
}

poj3352:贴一下大牛的解法,我只会第二种。

双连通分量

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

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

双连通分量分为【点双连通分量,边双连通分量】,这题是个边双连通分量,就是要求出整个图的边双连通分量,然后缩点,然后找出缩点后每个点的度,度为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

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
#define N 1010
#define M 1010
#define INF 0x3f3f3f3f int n,tot;
int head[N];
struct edge
{
int u,v,next;
}e[*M];
int dfn[N],low[N],vis[N],dcnt,bcnt,de[N]; inline int min(int x , int y)
{
return x<y ? x:y;
} void add(int u ,int v ,int k)
{
e[k].u = u; e[k].v = v;
e[k].next = head[u]; head[u] = k++;
u = u^v; v = u^v; u = u^v;
e[k].u = u; e[k].v = v;
e[k].next = head[u]; head[u] = k++;
} void dfs(int u ,int fa)
{
dfn[u] = low[u] = ++dcnt;
vis[u] = ;
for(int k=head[u]; k!=-; k=e[k].next)
{
int v = e[k].v;
if(v == fa) continue;
if(!vis[v]) //树边
{
dfs(v,u);
low[u] = min(low[u] , low[v]);
}
else if(vis[v] == ) //后向边
low[u] = min(low[u] , dfn[v]);
//如果是横叉边为vis[v] == 2 , 跳过
}
vis[u] = ;
} void solve()
{
memset(dfn,,sizeof(dfn));
memset(de,,sizeof(de));
memset(vis,,sizeof(vis));
dcnt = bcnt = ;
for(int i=; i<=n; i++)
if(!vis[i])
dfs(i,i);
for(int u=; u<=n; u++)
for(int k=head[u]; k!=-; k=e[k].next)
{
int v = e[k].v;
if(low[u] != low[v]) //属于不同的边连通分量
de[low[u]]++;
}
int leaf = ;
for(int i=; i<=n; i++)
if(de[i] == )
leaf++;
cout << (leaf+)/ << endl;
} int main()
{
while(cin>> n >> tot)
{
int u,v,k = ;
memset(head,-,sizeof(head));
for(int i=; i<tot; i++,k+=)
{
cin >> u >> v;
add(u,v,k);
}
solve();
}
return ;
}

方法2:这个代码慢啊,150ms,而且不知道模板还有没有其他的问题,至少是还没能处理重边

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
#define N 1010
#define M 1010
#define min(a,b) ((a)<(b)?(a):(b)) int n,tot;
int head[N],dfn[N],low[N],belong[N],de[N],stack[N],bridge[M][],ins[N],dcnt,bcnt,top,bnum;
struct edge
{
int u,v,next;
}e[*M]; void add(int u ,int v ,int k)
{
e[k].u = u; e[k].v = v; e[k].next = head[u]; head[u] = k++;
u = u^v; v = u^v; u = u^v;
e[k].u = u; e[k].v = v; e[k].next = head[u]; head[u] = k++;
} void dfs(int u ,int fa)
{
dfn[u] = low[u] = ++dcnt;
stack[++top] = u; ins[u] = ;
for(int k=head[u]; k!=-; k=e[k].next)
{
int v = e[k].v;
if(v == fa) continue;
if(!dfn[v]) //树边
{
dfs(v,u);
low[u] = min(low[u] , low[v]);
if(low[v] > dfn[u]) //边(u,v)为桥,可以统计一个边连通分支
{
//保存桥
bridge[bnum][] = u;
bridge[bnum++][] = v; ++bcnt;
while(true)
{
int x = stack[top--];
ins[x] = ;
belong[x] = bcnt;
if(x == v) break;
}//注意点u并没有出栈,因为点u属于另一个边连通分量
}
}
else if(ins[v]) //后向边
low[u] = min(low[u] , dfn[v]);
//横叉边为(dfn[v] && !ins[v]),跳过
}
} void solve()
{
memset(dfn,,sizeof(dfn));
memset(de,,sizeof(de));
memset(ins,,sizeof(ins));
dcnt = bcnt = top = bnum = ;
dfs(,-);
if(top)
{
++bcnt;
while(true)
{
int x = stack[top--];
ins[x] = ;
belong[x] = bcnt;
if(x == ) break;
}
} for(int i=; i<bnum; i++) //取出所有的桥
{
int u = bridge[i][];
int v = bridge[i][];
de[belong[u]]++;
de[belong[v]]++;
//统计缩点后的的度
}
int leaf = ;
for(int i=; i<=bcnt; i++)
if(de[i] == )
leaf++;
cout << (leaf+)/ << endl; //可以把下面的注释去掉,看看记录的内容,帮组理解
/*
for(int u=1; u<=n; u++)
cout << u << "[" << belong[u] << "]" << endl;
for(int i=1; i<=bcnt; i++)
cout << "[" << de[i] << "]" << endl;
for(int i=0; i<bnum; i++)
{
int u = bridge[i][0], v = bridge[i][1];
printf("桥: %d %d\n",u,v);
printf("缩点后的边: %d %d\n",belong[u] , belong[v]);
}
*/
} int main()
{
while(cin >> n >> tot)
{
memset(head,-,sizeof(head));
int u,v,k=;
for(int i=; i<tot; i++,k+=)
{
cin >> u >> v;
add(u,v,k);
}
solve();
}
return ;
}

POJ3352Road Construction(构造双连通图)sdut2506完美网络的更多相关文章

  1. poj3352Road Construction 边双连通+伪缩点

    /* 对于边双连通分支,求法更为简单. 仅仅需在求出全部的桥以后,把桥边删除.\ 原图变成了多个连通块,则每一个连通块就是一个边双连通分支. 桥不属于不论什么 一个边双连通分支,其余的边和每一个顶点都 ...

  2. POJ 3177--Redundant Paths【无向图添加最少的边成为边双连通图 &amp;&amp; tarjan求ebc &amp;&amp; 缩点构造缩点树】

    Redundant Paths Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 10798   Accepted: 4626 ...

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

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

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

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

  5. POJ3352-Road Construction(边连通分量)

    It's almost summer time, and that means that it's almost summer construction time! This year, the go ...

  6. POJ3352Road Construction(无向图强连通)

    http://poj.org/problem?id=3352 无向图强连通分量缩点 知道一个等式: 若要使得任意一棵树,在增加若干条边后,变成一个双连通图,那么 至少增加的边数 =( 这棵树总度数为1 ...

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

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

  8. POJ3177tarjan缩点_构建双连通图

    POJ3177tarjan缩点_构建双连通图 根据题意利用tarjan算法进行缩点处理后变成连通无环图,也可以说是一颗树,而且边是双向的所以,如果把这个图变成双连通,那就要对所有度为1的点进行加边处理 ...

  9. POJ 3177——Redundant Paths——————【加边形成边双连通图】

    Redundant Paths Time Limit:1000MS     Memory Limit:65536KB     64bit IO Format:%I64d & %I64u Sub ...

随机推荐

  1. 记录下自己常用的全框架HTML代码

    纯粹记录下,没有任何意义. 也不推荐使用 <frameset rows="> <frame src=" name="topFrame" scr ...

  2. sql替换数据库字段中的字符

    UPDATE `table_name` SET `field_name` = replace (`field_name`,'from_str','to_str') WHERE ……说明:table_n ...

  3. foreach使用

    1. 读取记录while($row=mysql_fetch_array($result)){$record[]=array(    'title'=>$row['title'], 'body'= ...

  4. C# 温故而知新:Stream篇(二)

    TextReader 和StreamReader 目录: 为什么要介绍 TextReader? TextReader的常用属性和方法 TextReader 示例 从StreamReader想到多态 简 ...

  5. Linux rdate 命令

    rdate命令可以用来查看远程服务器的时间,也可以同步远程服务器的时间到本机 [root@localhost ~]$ yum install -y rdate [root@localhost ~]$ ...

  6. 第四篇:GPU 并行编程的存储系统架构

    前言 在用 CUDA 对 GPU 进行并行编程的过程中,除了需要对线程架构要有深刻的认识外,也需要对存储系统架构有深入的了解. 这两个部分是 GPU 编程中最为基础,也是最为重要的部分,需要花时间去理 ...

  7. M0 M4之Timer初始化

    新唐的定时器一般有很多功能:普通的定时功能,事件计数功能,捕获功能,超时触发ADC等等.大家如果感兴趣可以读一下<NANOB Timer功能介绍以及在弱灌注中的应用.pdf>,虽然各个系列 ...

  8. 使用C#语言实现一些功能

    今天由于是周六,所以就没讲课啦,于是我就仔细看啦几道还没掌握的题,然后总结啦一下. 一.三级联动 像这个三级联动吧,感觉在做网站时间肯定会用到啦,但是那时间肯定不会是这样子做的啦,不可能把所有的省市区 ...

  9. iOS 录音功能的实现

    这两天也调了一下ios的录音,原文链接:http://www.iphoneam.com/blog/index.php?title=using-the-iphone-to-record-audio-a- ...

  10. Java初学者笔记一:元类、获取类型、枚举

    零.绪论: 2018年新年伊始,学习Java的冲动越来越强烈,毕竟以后无论是做安全开发还是安全研究都必不可少的掌握这门语言,所以在不断完善Python作为脚本语言的主语言的情况下觉得学习Java作为高 ...