题目大意:
沙漠中有N个城市编号1 ~ N(2 ≤ N ≤ 1,000),首都是1号城市,只有1号城市有水源,现在需要修管道使首都的水能引导其它所哟城市,要求其它所有城市到首都的路径唯一,但是各个城市位于不同的海拔,而水管只能是水平铺设,因此连接一个水管的两个城市还需要一个抽水泵以便将水从低处导向高处或从高处引向低处,现保证输入的各个城市在水平平面内不会出现三点共线的情况,并且各个城市之间海拔不相同。
现有多个测例,每个测例都给出N个城市的坐标信息"x y z",表示该城市的坐标为(x, y, z),其中0 ≤ x, y < 10,000,0 ≤ z < 10,000,000,都为整型数,其中管道长度即为水平平面内的欧几里得距离(即xOy平面),抽水泵的费用为两个城市之间的高度差,现在要求出如何铺设管道才能使抽水泵费用总和比上管道总长最小,并输出该最小比率并四舍五入至3为小数,输入以N = 0表示结束。
背景:
由于各点到1号点路径唯一,则必定是求一棵生成树,可以把各海拔差看做各点的点权值,若以1号点作为参考海拔,则1号点的点权值即为0,其余点的权值即为高度差,而边权值即为两点之间的欧几里得距离。
即求r = (C1+C2+…+Cn-1)/(L1+L2+…+Ln-1),其中r为比率,C为点权值,L为边权值,共N - 1个点(1号点权值为0)N - 1条边(生成树),并且要求r最小。
现在设f(r) = r*L1-C1 + r*L2-C2 +…+ r*Ln-1-Cn-1,则f(r_min) ≤ 0且f(r_max) ≥ 0,并且f(r)是单调递增的,当然r_min可以取无穷小但是这是没有意义的,当r_min为无穷小时f(r)必定<0,因此可以先大致定一个较大的范围[r_min, r_max],使得f(r_min) < 0且f(r_max) > 0,并用二分法使r逼近f(r)刚好≥0的点。
那现在的问题是该如何求f(r)呢?以r*Li-Ci为边求最大生成树(Prim+堆优化)就行了,因此二分法求最优比率生成树的算法就描述完了。
但是还有更快的方法,那就是迭代求f(r),现定一个初始值r0,然后每次用Prim时都会得到一个对应的原图的生成树,prim返回两个值,一个是f(r)还有一个就是原图对应的生成树的r(r由prim直接返回,f(r)可以通过静态变量返回),因此迭代算法为for ( r = r0; |f(r)| < ε; r = prim(r) )即可,迭代法求出的f(r)无限向0逼近,并且逼近时围绕0上下都抖动。
二分:
注释代码:
/*
* Problem ID : POJ 2728 Desert King
* Author : Lirx.t.Una
* Language : C++
* Run Time : 2469 ms
* Run Memory : 26568 KB
*/
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <queue>
//城市最大数量
#define MAXN 1000
//二分精度
#define EPS 1E-5
using namespace std;
struct Point {//各个城市坐标信息
int x, y, z;
friend istream &
operator>>(istream &is, Point &p) {
is >> p.x >> p.y >> p.z;
return is;
}
double
POW(int x) { return x * x; }
double
operator^(Point &oth) {//平面上的距离
return sqrt(POW( x - oth.x ) + POW( y - oth.y ));
}
int
operator&(Point &oth) {//海拔差
return abs(z - oth.z);
}
};
struct Node {//Prim+堆优化的结点
int u;
double d;
Node(void) {}
Node(int uu, double dd) : u(uu), d(dd) {}
bool//大顶堆求最大生成树
operator<(const Node &oth)
const {
return d < oth.d;
}
};
double g[MAXN + 1][MAXN + 1];//r*Li-Ci图中各点之间的边的权值
Point p[MAXN + 1];//各个点
//原图上个点之间的平面欧几里得距离以及海拔差
double dist[MAXN + 1][MAXN + 1];
int high[MAXN + 1][MAXN + 1];
//Prim数据域
bool vis[MAXN + 1];
double d[MAXN + 1];
int n;//城市个数
double
prim(double r) {
int i;
int u, v;
int ne;
double ans;//最大生成树返回的f(r)
Node node;
priority_queue<Node> heap;
memset(vis, 0, sizeof(vis));
for ( u = 1; u <= n; u++ )
for ( v = u + 1; v <= n; v++ )//Prim需要用到全边g[u][v]、g[v][u]
g[u][v] = g[v][u] = r * dist[u][v] - high[u][v];//r*Li-Ci构造新图
vis[1] = true;
for ( i = 2; i <= n; i++ ) {
d[i] = g[1][i];
heap.push(Node( i, d[i] ));
}
ans = 0.0;
ne = 1;
while (true) {
while (true) {
node = heap.top();
heap.pop();
if ( !vis[ u = node.u ] ) {
vis[u] = true;
ans += node.d;
ne++;
break;
}
}
if ( ne == n ) break;
for ( v = 2; v <= n; v++ )
if ( !vis[v] && g[u][v] > d[v] ) {
d[v] = g[u][v];
heap.push(Node( v, d[v] ));
}
}
return ans;
}
int
main() {
int i, j;
double lft, rht, mid;//二分
double r;//二分比率
while ( scanf("%d", &n), n ) {
for ( i = 1; i <= n; i++ ) cin >> p[i];
for ( i = 1; i <= n; i++ )//只需要用到半边
for ( j = i + 1; j <= n; j++ ) {
dist[i][j] = p[i] ^ p[j];
high[i][j] = p[i] & p[j];
}
//初始二分区间(经验值)
lft = 0.0;
rht = 100.0;
while ( rht - lft > EPS ) {
mid = ( lft + rht ) * 0.5;
if ( prim(mid) >= 0.0 ) rht = mid;
else lft = mid;
}
printf("%.3lf\n", rht);//一定要求≥0的一端
}
return 0;
}
无注释代码:
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <queue>
#define MAXN 1000
#define EPS 1E-5
using namespace std;
struct Point {
int x, y, z;
friend istream &
operator>>(istream &is, Point &p) {
is >> p.x >> p.y >> p.z;
return is;
}
double
POW(int x) { return x * x; }
double
operator^(Point &oth) {
return sqrt(POW( x - oth.x ) + POW( y - oth.y ));
}
int
operator&(Point &oth) {
return abs(z - oth.z);
}
};
struct Node {
int u;
double d;
Node(void) {}
Node(int uu, double dd) : u(uu), d(dd) {}
bool
operator<(const Node &oth)
const {
return d < oth.d;
}
};
double g[MAXN + 1][MAXN + 1];
Point p[MAXN + 1];
double dist[MAXN + 1][MAXN + 1];
int high[MAXN + 1][MAXN + 1];
bool vis[MAXN + 1];
double d[MAXN + 1];
int n;
double
prim(double r) {
int i;
int u, v;
int ne;
double ans;
Node node;
priority_queue<Node> heap;
memset(vis, 0, sizeof(vis));
for ( u = 1; u <= n; u++ )
for ( v = u + 1; v <= n; v++ )
g[u][v] = g[v][u] = r * dist[u][v] - high[u][v];
vis[1] = true;
for ( i = 2; i <= n; i++ ) {
d[i] = g[1][i];
heap.push(Node( i, d[i] ));
}
ans = 0.0;
ne = 1;
while (true) {
while (true) {
node = heap.top();
heap.pop();
if ( !vis[ u = node.u ] ) {
vis[u] = true;
ans += node.d;
ne++;
break;
}
}
if ( ne == n ) break;
for ( v = 2; v <= n; v++ )
if ( !vis[v] && g[u][v] > d[v] ) {
d[v] = g[u][v];
heap.push(Node( v, d[v] ));
}
}
return ans;
}
int
main() {
int i, j;
double lft, rht, mid;
double r;
while ( scanf("%d", &n), n ) {
for ( i = 1; i <= n; i++ ) cin >> p[i];
for ( i = 1; i <= n; i++ )
for ( j = i + 1; j <= n; j++ ) {
dist[i][j] = p[i] ^ p[j];
high[i][j] = p[i] & p[j];
}
lft = 0.0;
rht = 100.0;
while ( rht - lft > EPS ) {
mid = ( lft + rht ) * 0.5;
if ( prim(mid) >= 0.0 ) rht = mid;
else lft = mid;
}
printf("%.3lf\n", rht);
}
return 0;
}
迭代:
注释代码:
/*
* Problem ID : POJ 2728 Desert King
* Author : Lirx.t.Una
* Language : C++
* Run Time : 391 ms
* Run Memory : 26424 KB
*/
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <queue>
#define MAXN 1000
//迭代的精度可以更加低一点
#define EPS 1E-4
using namespace std;
struct Point {
int x, y, z;
friend istream &
operator>>(istream &is, Point &p) {
is >> p.x >> p.y >> p.z;
return is;
}
double
POW(int x) { return x * x; }
double
operator^(Point &oth) {
return sqrt(POW( x - oth.x ) + POW( y - oth.y ));
}
int
operator&(Point &oth) {
return abs(z - oth.z);
}
};
struct Node {
int u;
double d;
Node(void) {}
Node(int uu, double dd) : u(uu), d(dd) {}
bool
operator<(const Node &oth)
const {
return d < oth.d;
}
};
double g[MAXN + 1][MAXN + 1];
Point p[MAXN + 1];
double dist[MAXN + 1][MAXN + 1];
int high[MAXN + 1][MAXN + 1];
int pre[MAXN + 1];
bool vis[MAXN + 1];
double d[MAXN + 1];
int n;
double ans;//r*Li-Ci图的最大生成树的边权之和
double
prim(double r) {
int i;
int u, v;
int ne;
double we, wv;//原图中生成树的边权和点权
Node node;
priority_queue<Node> heap;
memset(vis, 0, sizeof(vis));
for ( u = 1; u <= n; u++ )
for ( v = u + 1; v <= n; v++ )
g[u][v] = g[v][u] = r * dist[u][v] - high[u][v];
vis[1] = true;
for ( i = 2; i <= n; i++ ) {
d[i] = g[1][i];
pre[i] = 1;
heap.push(Node( i, d[i] ));
}
ans = 0.0;
ne = 1;
we = 0.0;
wv = 0.0;
while (true) {
while (true) {
node = heap.top();
heap.pop();
if ( !vis[ u = node.u ] ) {
vis[u] = true;
ans += node.d;
we += dist[ pre[u] ][u];
wv += high[ pre[u] ][u];
ne++;
break;
}
}
if ( ne == n ) break;
for ( v = 2; v <= n; v++ )
if ( !vis[v] && g[u][v] > d[v] ) {
d[v] = g[u][v];
pre[v] = u;
heap.push(Node( v, d[v] ));
}
}
return wv / we;//返回原图对应生成树的比率
}
int
main() {
int i, j;
double r;
while ( scanf("%d", &n), n ) {
for ( i = 1; i <= n; i++ ) cin >> p[i];
for ( i = 1; i <= n; i++ )
for ( j = i + 1; j <= n; j++ ) {//由于prim中需要计算原图的r,因此要计算全边
dist[i][j] = dist[j][i] = p[i] ^ p[j];
high[i][j] = high[j][i] = p[i] & p[j];
}
//迭代初值取经验值9,ans只要>EPS就行了
for ( r = 9, ans = 1; fabs(ans) > EPS; r = prim(r) );
printf("%.3lf\n", r);
}
return 0;
}
无注释代码:
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <queue>
#define MAXN 1000
#define EPS 1E-4
using namespace std;
struct Point {
int x, y, z;
friend istream &
operator>>(istream &is, Point &p) {
is >> p.x >> p.y >> p.z;
return is;
}
double
POW(int x) { return x * x; }
double
operator^(Point &oth) {
return sqrt(POW( x - oth.x ) + POW( y - oth.y ));
}
int
operator&(Point &oth) {
return abs(z - oth.z);
}
};
struct Node {
int u;
double d;
Node(void) {}
Node(int uu, double dd) : u(uu), d(dd) {}
bool
operator<(const Node &oth)
const {
return d < oth.d;
}
};
double g[MAXN + 1][MAXN + 1];
Point p[MAXN + 1];
double dist[MAXN + 1][MAXN + 1];
int high[MAXN + 1][MAXN + 1];
int pre[MAXN + 1];
bool vis[MAXN + 1];
double d[MAXN + 1];
int n;
double ans;
double
prim(double r) {
int i;
int u, v;
int ne;
double we, wv;
Node node;
priority_queue<Node> heap;
memset(vis, 0, sizeof(vis));
for ( u = 1; u <= n; u++ )
for ( v = u + 1; v <= n; v++ )
g[u][v] = g[v][u] = r * dist[u][v] - high[u][v];
vis[1] = true;
for ( i = 2; i <= n; i++ ) {
d[i] = g[1][i];
pre[i] = 1;
heap.push(Node( i, d[i] ));
}
ans = 0.0;
ne = 1;
we = 0.0;
wv = 0.0;
while (true) {
while (true) {
node = heap.top();
heap.pop();
if ( !vis[ u = node.u ] ) {
vis[u] = true;
ans += node.d;
we += dist[ pre[u] ][u];
wv += high[ pre[u] ][u];
ne++;
break;
}
}
if ( ne == n ) break;
for ( v = 2; v <= n; v++ )
if ( !vis[v] && g[u][v] > d[v] ) {
d[v] = g[u][v];
pre[v] = u;
heap.push(Node( v, d[v] ));
}
}
return wv / we;
}
int
main() {
int i, j;
double r;
while ( scanf("%d", &n), n ) {
for ( i = 1; i <= n; i++ ) cin >> p[i];
for ( i = 1; i <= n; i++ )
for ( j = i + 1; j <= n; j++ ) {
dist[i][j] = dist[j][i] = p[i] ^ p[j];
high[i][j] = high[j][i] = p[i] & p[j];
}
for ( r = 9, ans = 1; fabs(ans) > EPS; r = prim(r) );
printf("%.3lf\n", r);
}
return 0;
}
单词解释:
prime:adj, 首席的
lifter:n, 升降机,起重机
vertical:adj, 垂直的
altitude:n, 海拔,高度
elegant:adj, 高雅的
ruler:n, 统治者
dominate:vt, 控制,支配
tyrant:n, 暴君
Terra Cotta Warriors:n, 兵马俑
terracotta:n, 陶瓦,吃土色
life-sized:adj, 原物大小的
mausoleum:n, 陵墓
gigantic:adj, 巨大的,庞大的
undertake:vt, 承担,从事
imperial:adj, 帝国的,皇帝的
unified:adj, 统一的
Warring States Period:n, 战国时代
warring:adj, 交战的,敌对的