【poj2728】Desert King

【题意】

给定一个含 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分数规划

【题解】

这个问题很像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=iGdixiiGcixi
变形得:
∑ 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 iGcixiansiGdixi=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)=iGcixiTiGdixi
同【前置知识】中的讲解的方法,我们可以确定如下二分法则:
1、若存在一个函数 f ( m i d ) &lt; 0 f(mid)&lt;0 f(mid)<0,则 m i d mid mid需向左移动;
2、若所有函数 f ( m i d ) &gt; 0 f(mid)&gt;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]midd[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);
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值