POJ 3177 / POJ 3352 : Redundant Paths / Road Construction - 边双连通分量,缩点

题意:
给定现有的R条直接连接2个牧场的路,F-1<=R<=10,000,计算至少需要新修多少条直接连接2个牧场的道路,使得任何2个牧场之间至少有2条独立的路。
分析:
见代码及注释……
《图论算法理论实现应用》——P412
3177Accepted700K16MSG++2320B2014-03-14 16:55:30

(啊,今天是过3177后的第三天晚上==!)
接着做练习题发现3177和3352是一模一样的……只是数据范围不同罢了。
 3352Accepted760K16MSG++2320B2014-03-16 20:18:33

/* POJ 3177;
 *边双连通分量里所有点可等价地看作一个点进行缩点——缩点后解决“在新图的树中至少添加多少条边能使图变为边双连通图”——添加边数=(书中度为1的结点数+1)/2.
 *采用并查集缩点,缩点后统计得到的树中叶子结点的个数,然后求应添加的边数
 */

#include<iostream>
#include<cstdio>
#include<cstring>
#define clr(a) memset(a,0,sizeof(a))
using namespace std;
const int N=1005,M=20005;
int n,m,w;//顶点数;边数;原图边双连通分量个数
int belong[N],low[N],dfn[N],visited[N];//	;顶点i可达祖先的最小编号;深度优先数;0未访问,1已访问,2已访问且已检查邻接顶点
int bridge[M][2],nbridge;

struct Node{
	int j;//另一顶点序号
	Node *next;//下一个边结点
};
Node mem[M];int memp;//储存边结点的数组;数组中的序号
Node *e[N];//邻接表

int MIN(int a,int b){
	return a>b?b:a;
}
/*邻接表中插入边(i,j)*/
void addEdge(Node *e[],int i,int j){
	Node *p=&mem[memp++];
	p->j=j;
	p->next=e[i];
	e[i]=p;
}

int FindSet(int f[],int i){
	int j=i,t;
	while(f[j]!=j)
		j=f[j];
	while(f[i]!=i){
		t=f[i];
		f[i]=j;
		i=t;
	}
	return j;//最后进行缩点的点
}

void UniteSet(int f[],int i,int j){
	int p=FindSet(f,i),q=FindSet(f,j);
	if(p!=q)
		f[p]=q;
}

/*搜索双连通分量——顶点,?,深度,并查集数组*/
void dfs_2conn(int i,int father,int dth,int f[]){
	int j,tofather=0;
	Node *p;
	visited[i]=1;
	low[i]=dfn[i]=dth;
	for(p=e[i];p!=NULL;p=p->next){
		j=p->j;
		if(visited[j]==1 && (j!=father||tofather))
			low[i]=MIN(low[i],dfn[j]);
		if(visited[j]==0){
			dfs_2conn(j,i,dth+1,f);
			low[i]=MIN(low[i],low[j]);
			if(low[j]<=dfn[i])//i,j在同一个双连通分量 则利用UnionSet缩点
				UniteSet(f,i,j);
			if(low[j]>dfn[i]){//(i,j)是桥
				bridge[nbridge][0]=i;
				bridge[nbridge++][1]=j;
			}
		}
		if(j==father)
			tofather=1;
	}
	visited[i]=2;
}
/*求无向图极大边双连通分量的个数*/
int DoubleConn(){
	int i,k,f[N],ncon=0;
	for(i=0;i<n;i++){
		f[i]=i;//并查集数组
		belong[i]=-1;
	}
	clr(visited);
	nbridge=0;
	dfs_2conn(0,-1,1,f);
	for(i=0;i<n;i++){
		k=FindSet(f,i);
		if(belong[k]==-1)
			belong[k]=ncon++;
		belong[i]=belong[k];
	}
	return ncon;
}

int main(){
	int i,j,k;
	while(~scanf("%d%d",&n,&m)){
		memp=0;
		clr(e);
		for(k=0;k<m;k++){
			scanf("%d%d",&i,&j);
			i--;j--;
			addEdge(e,i,j);addEdge(e,j,i);//相互地,故(i,j),(j,i)都插入链表
		}
		w=DoubleConn();
		int d[N]={0};//收缩后各顶点度数
		for(k=0;k<nbridge;k++){
			i=bridge[k][0];j=bridge[k][1];
			d[belong[i]]++;d[belong[j]]++;
		}

		int cnt=0;//缩点以后 叶子结点个数
		for(i=0;i<w;i++)
			if(d[i]==1)
				cnt++;
		printf("%d\n",(cnt+1)/2);
	}
	return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值