【题意】
给定一个含 n n n个点,每个点的空间坐标已知,现将它们两两相连构成一个完全图。对每一条边 ( i , j ) (i,j) (i,j),规定: c [ i ] [ j ] = ∣ z [ i ] − z [ j ] ∣ , d [ i ] [ j ] = ( x [ i ] − x [ j ] ) 2 + ( y [ i ] − y [ j ] ) 2 c[i][j]=|z[i]-z[j]|,d[i][j]=\sqrt{(x[i]-x[j])^2+(y[i]-y[j])^2} c[i][j]=∣z[i]−z[j]∣,d[i][j]=(x[i]−x[j])2+(y[i]−y[j])2,求一棵最小生成树,使 ∑ c [ i ] [ j ] ∑ d [ i ] [ j ] \frac{\sum_{}c[i][j]}{\sum_{}d[i][j]} ∑d[i][j]∑c[i][j]最小化。
【前置知识】
【题解】
这个问题很像01分数规划的形式。我们将
{
x
i
}
\{x_i\}
{xi}看作是否选择这条边,而且选出来的边恰好构成一个树。
令答案为
a
n
s
ans
ans,一条边的权值为
c
i
c_i
ci,
d
i
d_i
di(对应上文的
c
c
c与
d
d
d)则:
a
n
s
=
∑
i
∈
G
c
i
∗
x
i
∑
i
∈
G
d
i
∗
x
i
ans=\frac{\sum_{i∈G}c_i*x_i}{\sum_{i∈G}d_i*x_i}
ans=∑i∈Gdi∗xi∑i∈Gci∗xi
变形得:
∑
i
∈
G
c
i
∗
x
i
−
a
n
s
∑
i
∈
G
d
i
∗
x
i
=
0
\sum_{i∈G}c_i*x_i-ans\sum_{i∈G}d_i*x_i=0
i∈G∑ci∗xi−ansi∈G∑di∗xi=0
令:
f
(
T
)
=
∑
i
∈
G
c
i
∗
x
i
−
T
∑
i
∈
G
d
i
∗
x
i
f(T)=\sum_{i∈G}c_i*x_i-T\sum_{i∈G}d_i*x_i
f(T)=i∈G∑ci∗xi−Ti∈G∑di∗xi
同【前置知识】中的讲解的方法,我们可以确定如下二分法则:
1、若存在一个函数
f
(
m
i
d
)
<
0
f(mid)<0
f(mid)<0,则
m
i
d
mid
mid需向左移动;
2、若所有函数
f
(
m
i
d
)
>
0
f(mid)>0
f(mid)>0,则
m
i
d
mid
mid需向友移动;
3、若所有函数
f
(
m
i
d
)
f(mid)
f(mid)非负,且存在一个函数
f
(
m
i
d
)
=
0
f(mid)=0
f(mid)=0,则mid为答案。
同理,mid的移动方向只于众多函数
f
(
m
i
d
)
f(mid)
f(mid)的最小值有关。由于要保证选择的边能构成一棵树,所以我们新建一个图,
(
i
,
j
)
(i,j)
(i,j)的权值为
c
[
i
]
[
j
]
−
m
i
d
∗
d
[
i
]
[
j
]
c[i][j]-mid*d[i][j]
c[i][j]−mid∗d[i][j],然后跑一边最小生成树即可。
注意,由于这个图是完全图,所以用不带优化的prim是最好的。
【代码】
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int mn = 1005;
struct point{
double x, y, z;
} p[mn];
double d[mn][mn], c[mn][mn], dis[mn];
bool vis[mn];
int n;
inline double prim(double x)
{
double ans = 0;
memset(vis, 0, sizeof vis);
int i, j;
for(i = 1; i <= n; i++)
dis[i] = 1e8;
dis[1] = 0;
for(i = 1; i <= n; i++)
{
double mins = 1e8;
int p = 0;
for(j = 1; j <= n; j++)
if(!vis[j] && dis[j] < mins)
mins = dis[j], p = j;
if(!p) continue;
ans += dis[p], vis[p] = 1;
for(j = 1; j <= n; j++)
if(p != j && dis[j] > c[p][j] - x * d[p][j])
dis[j] = c[p][j] - x * d[p][j];
}
return ans;
}
int main()
{
int i, j;
while(~scanf("%d", &n) && n)
{
for(i = 1; i <= n; i++)
scanf("%lf%lf%lf", &p[i].x, &p[i].y, &p[i].z);
for(i = 1; i <= n; i++)
for(j = i + 1; j <= n; j++)
d[i][j] = d[j][i] = sqrt((p[i].x - p[j].x) * (p[i].x - p[j].x) + (p[i].y - p[j].y) * (p[i].y - p[j].y)), c[i][j] = c[j][i] = abs(p[i].z - p[j].z);
double l = 0, r = 1e5;
while(r - l > 1e-5)
{
double mid = (l + r) / 2, mins = prim(mid);
if(mins >= 0)
l = mid;
else
r = mid;
}
printf("%.3lf\n", r);
}
}