POJ2728 Desert King
一道生成树+\(0/1\)分数规划
原题链接
设每条边的距离为\(dis[x]\),两点高度差为\(h[x]\),该图的生成树为\(T\),则题目实际求的就是\(\dfrac{\sum\limits_{x\in T}h[x]}{\sum\limits_{x\in T}dis[x]}\)的最小值。
这就是经典的\(0/1\)分数规划问题。
这里我用的是二分法。二分答案,记为\(mid\)。将图上的边权全部改为\(h[x]-mid\times 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\n", l);
}
return 0;
}
posted on 2018-09-02 11:30 Iowa_Battleship 阅读(82) 评论(0) 编辑 收藏 举报