HDU-3177 Redundant Paths 无向图双连通

http://poj.org/problem?id=3177


/*

大致题意:
       为了保护放牧环境,避免牲畜过度啃咬同一个地方的草皮,牧场主决定利用不断迁移牲畜进行喂养的方法去保护牧草。然而牲畜在迁移过程中也会啃食路上的牧草,所以如果每次迁移都用同一条道路,那么该条道路同样会被啃咬过度而遭受破坏。
       现在牧场主拥有F个农场,已知这些农场至少有一条路径连接起来(不一定是直接相连),但从某些农场去另外一些农场,至少有一条路可通行。为了保护道路上的牧草,农场主希望再建造若干条道路,使得每次迁移牲畜时,至少有2种迁移途径,避免重复走上次迁移的道路。已知当前有的R条道路,问农场主至少要新建造几条道路,才能满足要求?
 
解题思路:
“使得每次迁移牲畜时,至少有2种迁移途径,避免重复走上次迁移的道路。”就是说当吧F个农场看作点、路看作边构造一个无向图G时,图G不存在桥。
 
那么可以建立模型:
       给定一个连通的无向图G,至少要添加几条边,才能使其变为双连通图。

2.求双连通分量以及构造双连通分量:
http://blog.csdn.net/lyy289065406/article/details/6762432
对于点双连通分支,实际上在求割点的过程中就能顺便把每个点双连通分支求出。建立一个栈,存储当前双连通分支,在搜索图时,每找到一条树枝边或后向边(非横叉边),就把这条边加入栈中。如果遇到某时满足DFS(u)<=Low(v),说明u是一个割点,同时把边从栈顶一个个取出,直到遇到了边(u,v),取出的这些边与其关联的点,组成一个点双连通分支。割点可以属于多个点双连通分支,其余点和每条边只属于且属于一个点双连通分支。
对于边双连通分支,求法更为简单。只需在求出所有的桥以后,把桥边删除,原图变成了多个连通块,则每个连通块就是一个边双连通分支。桥不属于任何一个边双连通分支,其余的边和每个顶点都属于且只属于一个边双连通分支。
一个有桥的连通图,如何把它通过加边变成边双连通图?方法为首先求出所有的桥,然后删除这些桥边,剩下的每个连通块都是一个双连通子图。把每个双连通子图收缩为一个顶点,再把桥边加回来,最后的这个图一定是一棵树,边连通度为1。
统计出树中度为1的节点的个数,即为叶节点的个数,记为leaf。则至少在树上添加(leaf+1)/2条边,就能使树达到边二连通,所以至少添加的边数就是(leaf+1)/2。具体方法为,首先把两个最近公共祖先最远的两个叶节点之间连接一条边,这样可以把这两个点到祖先的路径上所有点收缩到一起,因为一个形成的环一定是双连通的。然后再找两个最近公共祖先最远的两个叶节点,这样一对一对找完,恰好是(leaf+1)/2次,把所有点收缩到了一起。
*/
#include<stdio.h>
#include<string.h>
#include<vector>
using namespace std;
const int maxn = 1005;
const int inf = 1<<30;
int n,m;
int time,ans;
int low[maxn],dfn[maxn],cut[maxn];
bool vis[maxn][maxn];
vector<int>map[maxn];

void tarjan( int u, int fa )
{
    low[u] = dfn[u] = ++time;
    for( int i = 0; i < map[u].size(); i ++ )
    {
        int v = map[u][i];
        if( v == fa )                     //判定回边
            continue;
        if( !dfn[v] )
        {
            tarjan( v,u );
            low[u] = low[u] <= low[v] ? low[u]:low[v];
        }
        else 
            low[u] = low[u] <= dfn[v] ? low[u]:dfn[v];
    }
}
void output()
{
	memset( cut,0,sizeof(cut) );
	int num = 0;
	for( int i = 1; i <= n; i++ )
	{
		for( int j = 0; j < map[i].size(); j ++ )         //计算每个点的度数
		{
			int v = map[i][j];
			if( low[v] != low[i] )             //不属于同一个块
			{
				cut[low[i]] ++;
			}
		}
	}
	for( int i = 0; i <= n; i ++ )                  //计算度数为一的点数
	{
		if( cut[i] == 1 )
			num ++;
	}
	printf("%d\n",(num+1)/2);
}

void init()                                        //初始化
{
    time = 0,ans = inf;
    for( int i = 1; i <= n; i ++ )
    {
            map[i].clear();
    }
    memset( low,0,sizeof(low) );
    memset( dfn,0,sizeof(dfn) );
}
int main()
{
    int u,v,d;
    while( scanf("%d%d",&n,&m) != EOF  )
    {
        init();
         for( int i = 1; i <= m; i ++ )
         {
             scanf("%d%d",&u,&v);
			 if( !vis[u][v] )
			 {
				 map[u].push_back(v);
				 map[v].push_back(u);
				 vis[u][v] = vis[v][u] = 1;
			 }
		 }
          tarjan(1,1);
		  output();
    }
    return 0;
}


//并查集版
#include<stdio.h>
#include<string.h>
#include<vector>
using namespace std;
const int maxn = 1005;
const int inf = 1<<30;
int n,m;
int time;
int low[maxn],dfn[maxn],p[maxn];
int bridge[maxn][2],bridge_n;
int inq[maxn];
vector<int>map[maxn];

int find( int x )
{
	return p[x] == x?x:p[x] = find(p[x]);
}
void merge( int a,int b )
{
	int x = find(a);
	int y = find(b);
	if( x == y )
		return;
	p[y] = x;
}
void tarjan( int u, int fa )
{
	int son = 0;              //平行边
    low[u] = dfn[u] = ++time;
    for( int i = 0; i < map[u].size(); i ++ )
    {
        int v = map[u][i];
        if( v == fa )                     //判定回边
            son ++;
        if( !dfn[v] )
        {
            tarjan( v,u );
            low[u] = low[u] <= low[v] ? low[u]:low[v];
			if( !(low[v] > dfn[u]) )    //缩点
			{
				merge(v,u);				
			}
			else
			{
				bridge[++bridge_n][0] = u;   //存割边
				bridge[bridge_n][1] = v;
			}
        }
        else if( v != fa || son != 1 )
            low[u] = low[u] <= dfn[v] ? low[u]:dfn[v];
    }
}
void output()
{
	memset( inq,0,sizeof(inq) );
	int ans = 0;
	for( int i = 1; i <= bridge_n; i++ )  //割边两端的块度数加加
	{
		int a = find( bridge[i][0] );
		int b = find( bridge[i][1] );
		inq[a] ++;
		inq[b] ++;
	}
	for( int i = 1; i <= n; i ++ )                  //计算度数为一的块数
	{
		if( inq[i] == 1 )
			ans ++;
	}
	printf("%d\n",(ans+1)/2);
}

void init()                                        //初始化
{
    time = 0,bridge_n = 0;
    for( int i = 1; i <= n; i ++ )
    {
            map[i].clear();
			p[i] = i;
    }
	memset( bridge,0,sizeof(bridge) );
    memset( low,0,sizeof(low) );
    memset( dfn,0,sizeof(dfn) );
}
int main()
{
    int u,v,d;
    while( scanf("%d%d",&n,&m) != EOF  )
    {
        init();
         for( int i = 1; i <= m; i ++ )
         {
             scanf("%d%d",&u,&v);
			 map[u].push_back(v);
			 map[v].push_back(u);
		 }
          tarjan(1,1);
		  output();
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值