贪心+double二分+最小生成树

图论O题
题意:空间里有n个点,连接出一条最小生成树保证高度之和/水平长度之和尽可能小。
题解:
考虑把生成树的总权值等价于:每条边的权值(暂时不会计算) w i w_i wi,每条边选择或者不选择为 x i x_i xi
W = ∑ i = 1 n ( n − 1 ) x i ∗ w i W=\sum^{n(n-1)}_{i=1}{x_i*w_i} W=i=1n(n1)xiwi
如果最后的答案是 a n s ans ans,显然满足 W = ∑ x i h i ∑ x i l i > = a n s W=\frac{\sum{x_ih_i}}{\sum{x_il_i}}>=ans W=xilixihi>=ans
可以转换成 ∑ x i ( h i − a n s ∗ l i ) ≥ 0 \sum{x_i(h_i-ans*l_i)}≥0 xi(hiansli)0
也就是对于 a n s ans ans,这样我们可以构建出一个当前最小生成树,如果能满足等于号即是答案。
所以我们需要枚举答案,但是显然是不行的,所以再考虑二分
显然 a n s ans ans减小的时候,左式变大,反之变小。也就是当最小生成树的权值和大于 0 0 0的时候。
应该增大 a n s ans ans,反之减小。
满足决策单调性的。
这题采用邻接矩阵,因为是 n 2 n^2 n2的边数。同时 d o u b l e double double二分值得参考。(改变端点的时候直接等于左右端点即可。注意:精度会影响速度。这题保留三位小数,用 1 e − 4 即 可 1e^{-4}即可 1e4)

#include<bits/stdc++.h>
#define FOR(i,a,b) for(int i=a;i<=b;i++)
#define eps 1e-4
using namespace std;

int n;
double line[1010][1010],height[1010][1010];

const int maxm = 2500000;
const int maxn = 2500;

struct node{
    double x,y,z;
}A[1010];

bool vis[maxn];double minc[maxn];

double prim(double x){
    memset(vis,0,sizeof(vis));
    memset(minc,0x7f,sizeof(minc));
    vis[1]=true;
    double small,ans=0;int now;
    for(int i=1;i<=n;i++)minc[i]=height[1][i]-x*line[1][i];
    for(int i=1;i<n;i++){
        small=0x7f7f7f7f;
        for(int j=1;j<=n;j++)if(minc[j]<small&&!vis[j]){small=minc[j];now=j;}
        ans+=small;vis[now]=true;
        for(int j=1;j<=n;j++)minc[j]=min(minc[j],height[now][j]-x*line[now][j]);
    }
    return ans;
}

bool check(double x){
    double judge=prim(x);
    if(abs(judge-0)<=eps)return true;
    if(judge>0)return true;
    return false;
}

int main(){
    cin>>n;
    FOR(i,1,n){
        scanf("%lf%lf%lf",&A[i].x,&A[i].y,&A[i].z);
    }
    double ans,tmp=0;
    FOR(i,1,n)FOR(j,i+1,n){
        line[i][j]=line[j][i]=sqrt((A[i].x-A[j].x)*(A[i].x-A[j].x)+(A[i].y-A[j].y)*(A[i].y-A[j].y));
        height[i][j]=height[j][i]=abs(A[i].z-A[j].z);
        tmp=line[i][j]*2+height[i][j]*2;
    }
    double l=0,r=tmp;
    while(r-l>eps){
        double mid=(l+r)/2.0;
        if(check(mid)){
            ans=mid;
            l=mid;//>=0
        }
        else r=mid;
    }
    printf("%.3lf\n",ans);
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值