POJ 2728 Desert King(算法竞赛进阶指南,最小生成树,0/1分数规划)

算法竞赛进阶指南368页,最小生成树,0/1分数规划
题目意思:
n个点,每个点以平面坐标 (x, y) 表示, 其海拔高度是 z。 点与点之间的距离就是 (x1, y1) 与 (x2, y2) 的
平面距离 (这里用数组 a[MaxN][MaxN] 来存)。
点与点之间的高度差值的绝对值,叫做成本(这里用数组 b[MaxN][MaxN] 来存)。
题目要求,找到一个图的生成树,使得这棵树 总的成本 sum{成本} / sum{距离} 最大。

本题要点:
1、 二分法,构造一个新的无向图, 边的权值 是 a[i][j] - mid * b[i][j] 。
然后二分 mid, 直到找到最小的 mid, 使得 这棵树的最大生成树 的总边权为0.
2、稠密图,套用 prim 算法,得到 特定 mid 值下的图中的最大生成树。
d[k] 表示 k点到这棵树的距离。 d[1] + d[2] + … + d[n] 就是这棵树的总的权值。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;
const int MaxN = 1010, INF = 0x3f3f3f3f;
int n;
double eps = 1e-6, a[MaxN][MaxN], b[MaxN][MaxN];
double d[MaxN];
bool vis[MaxN];

struct node
{
	int x, y, z;
}village[MaxN];

bool prim(double mid)
{
	memset(vis, false, sizeof vis);
	for(int i = 0; i <= n; ++i)
		d[i] = INF;
	d[1] = 0;
	double ans = 0;
	for(int i = 1; i < n; ++i)		
	{
		int x = 0;
		for(int j = 1; j <= n; ++j)
		{
			if(!vis[j] && (0 == x || d[j] < d[x]))
				x = j;
		}
		if(!x)	break;
		vis[x] = 1;
		for(int y = 1; y <= n; ++y)
		{
			if(!vis[y])
			{
				if(d[y] > a[x][y] - mid * b[x][y])
				{
					d[y] = a[x][y] - mid * b[x][y];
				}
			}
		}
	}
	for(int i = 1; i <= n; ++i)
		ans += d[i];
	return ans >= 0;
}

void solve()
{
	double x1, y1, r = 0;
	for(int i = 1; i <= n; ++i)
	{
		for(int j = i + 1; j <= n; ++j)
		{
			a[i][j] = a[j][i] = abs(village[i].z - village[j].z);
			r += a[i][j];
			x1 = village[i].x - village[j].x, y1 = village[i].y - village[j].y;
			b[i][j] = b[j][i] = sqrt(x1 * x1 + y1 * y1);		
		}
	}
	double l = 0, mid = (l + r) / 2;
	while(r - l > eps)
	{
		mid = (l + r) / 2;	
		if(prim(mid))
		{
			l = mid;
		}else{
			r = mid;
		}
	}
	printf("%.3f\n", l);
}

int main()
{
	while(scanf("%d", &n) != EOF && n)
	{
		for(int i = 1; i <= n; ++i)
		{
			scanf("%d%d%d", &village[i].x, &village[i].y, &village[i].z);
		}
		solve();
	}
	return 0;
}

/*
4
0 0 0
0 1 1
1 1 2
1 0 3
0
*/

/*
1.000
*/

猜你喜欢

转载自blog.csdn.net/qq_38232157/article/details/108425474