POJ 3177 Redundant Paths + POJ 3352 Road Construction


POJ 3177 Redundant Paths题目链接:http://poj.org/problem?id=3177

POJ 3352 Road Construction题目链接:http://poj.org/problem?id=3352

这两题很好玩,能用同样的代码过这两题。而且POJ上的数据都很弱,即使网上80%的代码都是错的,也照样能ac。

题目的话明显就是求至少加入几条边能使图变成边双连通图。

---------------下面是转载自優YoU http://blog.csdn.net/lyy289065406/article/details/6762370,但是他的一部分思路错了,所以我改了下---------------------------------------------------

首先建立模型:

       给定一个连通的无向图G,至少要添加几条边,才能使其变为双连通图。

 

       模型很简单,正在施工的道路我们可以认为那条边被删除了。那么一个图G能够在删除任意一条边后,仍然是连通的,当且仅当图G至少为双连通的。

       PS:不要问我为什么不是3-连通、4-连通...人家题目问“至少添加几条边”好不...

 

       显然,当图G存在桥(割边)的时候,它必定不是双连通的。桥的两个端点必定分别属于图G的两个【边双连通分量】(注意不是点双连通分量),一旦删除了桥,这两个【边双连通分量】必定断开,图G就不连通了。但是如果在两个【边双连通分量】之间再添加一条边,桥就不再是桥了,这两个【边双连通分量】之间也就是双连通了。

 

       那么如果图G有多个【边双连通分量】呢?至少应该添加多少条边,才能使得任意两个【边双连通分量】之间都是双连通(也就是图G是双连通的)?

 

       这个问题就是本题的问题。要解决这个问题:

 

1、  首先要找出图G的所有【边双连通分量】。 

2、  把每一个【边双连通分量】都看做一个点(即【缩点】)

也有人称【缩点】为【块】,都是一样的。其实缩点不是真的缩点,图G的点分类处理,就已经缩点了。

以样例1为例,样例1得到的图G为上左图,

 

3、  问题再次被转化为“至少在缩点树上增加多少条树边,使得这棵树变为一个双连通图”。

首先知道一条等式:

若要使得任意一棵树,在增加若干条边后,变成一个双连通图,那么

至少增加的边数 =( 这棵树总度数为1的结点数 + 1 )/ 2

(证明就不证明了,自己画几棵树比划一下就知道了)

 

那么我们只需求缩点树中总度数为1的结点数(即叶子数)有多少就可以了。换而言之,我们只需求出所有缩点的度数,然后判断度数为1的缩点有几个,问题就解决了。

 

4、  求出所有缩点的度数的方法

两两枚举图G的直接连通的点,只要这两个点不在同一个【缩点】中,那么它们各自所在的【缩点】的度数都+1。注意由于图G时无向图,这样做会使得所有【缩点】的度数都是真实度数的2倍,必须除2后再判断叶子。

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

注意原博客中求边的双联通分量的时候,用的是tarjan算法中的low[]数组。他认为low[]数组相同的就是在同一个连通分量。事实证明是错的。因为对于

用例:

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

他的程序中给出的代码跑出来的结果是1,其实结果应该是2,其中low[11]用tarjan求的话应该等于3,low[2] = low[5] = low[6] = 2,但事实上点11和点2,5,6是同一个边连通分量中的。

 

既然不能用low[]的话我们就可以用下面这种方法:

1,。求出所有的割边(桥),

2.删去所有的割边后用dfs计算共有几个连通分量,其中每个连通分量重新缩为

3。记录缩点后的每个点的度数,然后按照上面的方法求。

 

#include<stdio.h>
#include<string.h>
#include<vector>
#include<stdlib.h>
#include<algorithm>
#define MEM(x,y) memset(x,y,sizeof(x))
#define INF 5100
using namespace std;

struct edge
{
	int to;
	int flag;		//标记该边是不是割边(桥)
	int id;			//存反向边在边集中的下标
}e[2*INF];

int low[INF],deep[INF];
vector<int>graph[INF];	//graph[i]存以i为起点的边在在边集e[]中的下标
int dfs_deep,F,R,cnt;
int du[INF];			//用来存储点的度数
int vis[INF];			//用来存在dfs是否已经遍历过该点,以及该点后来缩点后所在的新的替代点
int lcount;				//记录删去桥后连通分支的个数
bool map[INF][INF];		//用来判断是否有重边

void addedge(int a,int b)	//加边
{
	map[a][b] = true;
	map[b][a] = true;
	e[cnt].to = b;
	e[cnt].id = cnt+1;
	graph[a].push_back(cnt);
	cnt++;
	e[cnt].to = a;
	graph[b].push_back(cnt);
	e[cnt].id = cnt-1;
	cnt++;
}

void tarjan(int node,int prenode)	//求割边
{
	low[node] = deep[node] = ++dfs_deep;
	int len = graph[node].size();
	for(int i = 0 ; i < len ; i ++)
	{
		int temp = graph[node][i];	//i为起点的边在e[]中的下标
		if(!deep[e[temp].to])
		{
			tarjan(e[temp].to,node);
			low[node] = min(low[node],low[e[temp].to]);
			if(low[e[temp].to] > deep[node])	//求割边,与求割点有所不同,没有等于号
			{
				e[temp].flag = 1;
				e[e[temp].id].flag = 1;
			}
		}
		else
			if(e[temp].to != prenode)		//如果不是父子边(树边)
				low[node] = min(low[node],deep[e[temp].to]);
	}
}

void dfs(int node)	//用来求当删除割边的时候,图中的连通分支
{
	vis[node] = lcount;
	int len = graph[node].size();
	for(int i = 0 ; i < len ; i ++)
	{
		int temp = graph[node][i];
		if(vis[e[temp].to] == 0 && e[temp].flag == 0)	//没遍历过且不是割边
			dfs(e[temp].to);
	}
}

int main()
{
		scanf("%d%d",&F,&R);
		MEM(low,0);
		MEM(deep,0);
		MEM(du,0);
		MEM(e,0);
		MEM(vis,0);
		MEM(map,0);
		cnt = 0;
		for(int i = 0 ; i <= F; i ++)
			graph[i].clear();
		while(R--)
		{
			int a,b;
			scanf("%d%d",&a,&b);
			if(map[a][b] == false && map[b][a] == false)
				addedge(a,b);
		}
		dfs_deep = 0;
		tarjan(1,0);

		lcount = 1;	//lcount存的是缩环后的替代点
		for(int i = 1 ;i <= F ; i ++)
		{
			if(vis[i] == 0)
			{
				dfs(i);
				lcount++;
			}
		}

		for(int i = 1; i <= F; i ++)
		{
			int len = graph[i].size();
			for(int j = 0 ; j < len ; j ++)
			{
				int temp = graph[i][j];
				if(vis[i] != vis[e[temp].to])	//如果不是在同一个替代点,即不是在一个边连通分量内
				{
					du[vis[i]]++;		//替代点的度数加
					du[vis[e[temp].to] ] ++;
				}
			}
		}

		int ans = 0;
		for(int i = 1; i < lcount; i ++)
		{
			du[i] = du[i]/2;
			if(du[i] == 1)
				ans++;
		}
		printf("%d\n",(ans+1)/2);
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值