POJ 3164 Command Network (最小树形图模板 朱刘算法)

Command Network
Time Limit: 1000MS Memory Limit: 131072K
Total Submissions: 16426 Accepted: 4731

Description

After a long lasting war on words, a war on arms finally breaks out between littleken’s and KnuthOcean’s kingdoms. A sudden and violent assault by KnuthOcean’s force has rendered a total failure of littleken’s command network. A provisional network must be built immediately. littleken orders snoopy to take charge of the project.

With the situation studied to every detail, snoopy believes that the most urgent point is to enable littenken’s commands to reach every disconnected node in the destroyed network and decides on a plan to build a unidirectional communication network. The nodes are distributed on a plane. If littleken’s commands are to be able to be delivered directly from a node A to another node B, a wire will have to be built along the straight line segment connecting the two nodes. Since it’s in wartime, not between all pairs of nodes can wires be built. snoopy wants the plan to require the shortest total length of wires so that the construction can be done very soon.

Input

The input contains several test cases. Each test case starts with a line containing two integer N (N ≤ 100), the number of nodes in the destroyed network, and M (M ≤ 104), the number of pairs of nodes between which a wire can be built. The next N lines each contain an ordered pair xi and yi, giving the Cartesian coordinates of the nodes. Then follow M lines each containing two integers i and j between 1 and N (inclusive) meaning a wire can be built between node i and node j for unidirectional command delivery from the former to the latter. littleken’s headquarter is always located at node 1. Process to end of file.

Output

For each test case, output exactly one line containing the shortest total length of wires to two digits past the decimal point. In the cases that such a network does not exist, just output ‘poor snoopy’.

Sample Input

4 6
0 6
4 6
0 0
7 20
1 2
1 3
2 3
3 4
3 1
3 2
4 3
0 0
1 0
0 1
1 2
1 3
4 1
2 3

Sample Output

31.19
poor snoopy

题意:给出一副有向图,每个点都有坐标,弧的权值都可以通过计算得到,求由该有向图的弧构成的最小树形图的权值和,若无法构成最小树形图,则输出 poor snoopy。

思路:用朱刘算法求最小树形图的权值和。总共有四步:

          1.求每个点的最小入弧的权值,并且相加,同时判断是否有除根外的无入弧节点。

若有,则无法构成最小树形图。

          2.通过vis数组(访问标记)和pre数组(存前驱点)来判断是否有环,具体过程为若从一个点开始,沿着前驱遍历,最终能走到开始点,说明存在环,若走到根节点,说明不存在环。

          3.若存在环,则将该环缩成一个点,即忽略环包括的点,同时用id数组标记环,后面还要用id数组重新标记其它所有点。若不存在环,则直接跳出循环并返回结果。

          4.重新构图,把所有点重新标号,用减法操作更新各条弧的权值,e[i].w -= in[v]

(e[i].w 是以v为弧头的弧的权值),这个操作是为了避免重复加权值。

代码如下:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#include<cmath>
#define MAXN 105
#define INF 0x3f3f3f3f 
using namespace std;
struct Edge
{
	int u, v;
	double w;
}e[MAXN * MAXN];
struct Point
{
	double x, y;
}p[MAXN];
double in[MAXN];
int pre[MAXN], id[MAXN], vis[MAXN];
double make_dis(Point p1, Point p2)
{
	return sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y));
}
double zhuliu(int root, int n, int m)
{
	double ret = 0;
	int i; 
	while(1)
	{
		//1.找最小入边 
		for(i = 1; i <= n; i++)
		{
			in[i] = INF;
		} 
		for(i = 1; i <= m; i++)
		{
			int uu = e[i].u;
			int vv = e[i].v;
			if(e[i].w < in[vv] && uu != vv)//重新构图后有些边u和v可能相等 
			{
				in[vv] = e[i].w;
				pre[vv] = uu;//找前驱 
			}
		}
		for(i = 1; i <= n; i++)
		{
			if(i == root)continue;
			if(in[i] == INF)return -1;//如果找到一个非根的点且无入边,则无法构成最小树形图 
		}
		//2.找环 
		int cnt = 0;
		memset(id, -1, sizeof(id));//初始化 
		memset(vis, -1, sizeof(vis));
		in[root] = 0;
		for(i = 1; i <= n; i++)
		{
			ret += in[i];//加最小入边 
			int v = i;
			while(vis[v] != i && id[v] == -1 && v != root)//判断是否有环 
			{
				vis[v] = i;//每走一个点对vis数组标记为i 
				v = pre[v];//沿着前驱走 
			}
			if(v != root && id[v] == -1)//如果有环则将环缩成点 
			{
				cnt++;//环数加1 
				for(int u = pre[v]; u != v; u = pre[u])
				{
					id[u] = cnt;
				}
				id[v] = cnt;
			}
		}
		if(cnt == 0)break;//无环则直接退出循环
		//3.重新构图 
	 	for(i = 1; i <= n; i++)//对其它的点重新标号 
	 	{
	 		if(id[i] == -1)id[i] = ++cnt;
	 	}
	 	for(i = 1; i <= m; i++)//对每条边的点重新赋值 
	 	{
        	int u = e[i].u;  
            int v = e[i].v;  
            e[i].u = id[u];  
           	e[i].v = id[v];  
           	if(id[u] != id[v]) e[i].w -= in[v];//每条还存在的边更新长度 
	 	}//减去上一轮该点的最小入边,防止重复计算 
 		n = cnt;
 		root = id[root];
	}
	return ret;
}
int main()
{
	int i, j, n, m;
	while(scanf("%d%d", &n, &m) != EOF)
	{
		for(i = 1; i <= n; i++)
		{
			scanf("%lf%lf", &p[i].x, &p[i].y);
		}
		for(i = 1; i <= m; i++)
		{
			scanf("%d%d", &e[i].u, &e[i].v);
			if(e[i].u != e[i].v)
			{
				e[i].w = make_dis(p[e[i].u], p[e[i].v]);
			}
			else
			{
				e[i].w = INF;//消除自环 
			}
		}
		
		double ans = zhuliu(1, n, m);
		if(ans == -1)printf("poor snoopy\n");
		else printf("%.2f\n",ans); //G++提交不能使用%lf 
	} 
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值