POJ2728 Desert King

一道生成树+(0/1)分数规划

原题链接

设每条边的距离为(dis[x]),两点高度差为(h[x]),该图的生成树为(T),则题目实际求的就是(dfrac{sumlimits_{xin T}h[x]}{sumlimits_{xin T}dis[x]})的最小值。
这就是经典的(0/1)分数规划问题。
这里我用的是二分法。二分答案,记为(mid)。将图上的边权全部改为(h[x]-mid imes dis[x]),然后在上面跑最小生成树并计算边权和,如果非负说明(mid)过小,否则过大。
另外,虽然理论二分上界为(10^7),不过数据水,上界开四五十基本就能过了。

#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
const int N = 1010;
struct dd {
	int x, y, z;
};
dd a[N];
int n;
double D[N][N], dis[N];
bool v[N];
int re()
{
	int x = 0;
	char c = getchar();
	bool p = 0;
	for (; c<'0' || c>'9'; c = getchar())
		p |= c == '-';
	for (; c >= '0'&&c <= '9'; c = getchar())
		x = x * 10 + (c - '0');
	return p ? -x : x;
}
inline double minn(double x, double y)
{
	return x < y ? x : y;
}
inline int jd(int x)
{
	return x < 0 ? -x : x;
}
inline double co(int x, int y, double mid)
{
	return -mid * D[x][y] + jd(a[x].z - a[y].z);
}
double judge(double mid)
{
	double s = 0;
	int i, j, x;
	memset(v, 0, sizeof(v));
	memset(dis, 66, sizeof(dis));
	dis[1] = 0;
	for (i = 1; i <= n; i++)
	{
		x = 0;
		for (j = 1; j <= n; j++)
			if (!v[j] && (dis[j] < dis[x] || !x))
				x = j;
		if (!x)
			break;
		v[x] = 1;
		s += dis[x];
		for (j = 1; j <= n; j++)
			if (!v[j])
				dis[j] = minn(dis[j], co(x, j, mid));
	}
	return s;
}
int main()
{
	int i, j;
	double l, r, mid;
	while (1)
	{
		n = re();
		if (!n)
			return 0;
		for (i = 1; i <= n; i++)
		{
			a[i].x = re();
			a[i].y = re();
			a[i].z = re();
		}
		for (i = 1; i < n; i++)
			for (j = i + 1; j <= n; j++)
				D[i][j] = D[j][i] = sqrt(1.0*(a[i].x - a[j].x)*(a[i].x - a[j].x) + 1.0*(a[i].y - a[j].y)*(a[i].y - a[j].y));
		l = 0;
		r = 1e7;
		while (l + 1e-4 < r)
		{
			mid = (l + r) / 2;
			if (judge(mid) >= 0)
				l = mid;
			else
				r = mid;
		}
		printf("%.3f
", l);
	}
	return 0;
}
原文地址:https://www.cnblogs.com/Iowa-Battleship/p/9573231.html