USACO 6.4.2 Electric Fences 三分算法

http://train.usaco.org/usacoprob2?a=gY0nBCREMEt&S=fence3

题目大意:用两点坐标形式给出平面内F条线段(平面为(0,0)到(100,100), F小于等于150),求出平面内一点使得从该点连向所有线段的距离最小(可以和线段上任意一点相连)且平面内的各种线可以任意交叉


解题思路:

一开始查到了模拟退火的解法,吓了我一跳,后来查到了这个亲民的解法:

https://www.cnblogs.com/albert7xie/p/5736315.html


这道题目一下子放在平面下又是小数坐标又是点与线段的三种位置情况的可能难有头绪,这时候我们就可以将其退化到直线上观察,发现直线上任意点到已知点的距离综合与该点的坐标是成一个二次函数的关系,我们就可以推导出在平面中当一个坐标固定时,点到所有线段的距离与另一个坐标也是成开口向上的二次函数的关系,如此一来我们就可以用教科书般的三分方法在一个坐标固定时求出另一个坐标的最佳位置和对应的最短距离。

至于固定的那个坐标,我们可以发现,当一个坐标固定时的最小距离与这个固定的坐标也是一个开口向上的二次函数关系(设(x0, y0)是最小值点,有x1>x0,y1是x=x1时的最小值位置,则f(x1, y1)>f(x0, y1)>=f(x0, y0))所以可以用三分套之前的三分。

至于计算点到线段的距离,我们可以先通过点乘来确定其位置关系,再视情况直接计算点与点的距离或通过叉乘获得最短距离


/*
ID: frontie1
TASK: fence3
LANG: C++
*/
#include <iostream>
#include <cstdio>
#include <cmath>

using namespace std;

double square(double num)
{
    return num*num;
}

double crossproduct(double a_x, double a_y, double b_x, double b_y)
{
    return (a_x*b_y - a_y*b_x);
}

double dotproduct(double a_x, double a_y, double b_x, double b_y)
{
    return (a_x*b_x +a_y*b_y);
}

double dist(double a_x, double a_y, double b_x, double b_y)
{
    return(sqrt(square(a_x-b_x)+square(a_y-b_y)));
}

struct line
{
    double a_x, a_y;
    double b_x, b_y;

    double length()
    {
        return(sqrt(square(a_x-b_x)+square(a_y-b_y)));
    }

    double distance(double x, double y)
    {
        if(dotproduct(a_x-b_x, a_y-b_y, x-b_x, y-b_y) <= 0) return dist(x, y, b_x, b_y);
        if(dotproduct(b_x-a_x, b_y-a_y, x-a_x, y-a_y) <= 0) return dist(x, y, a_x, a_y);
        else return(abs(crossproduct(b_x-a_x, b_y-a_y, x-a_x, y-a_y))/(this->length()));
    }
};

int F;
line fence[160];
double output_x, output_y, output_dist = 0x3f3f3f3f;

double get_total(double x, double y)
{
    double output = 0;
    for(int i = 0; i < F; ++i){
        output += fence[i].distance(x, y);
    }
    return output;
}

double try_y(double x)
{
    double lo = 0, hi = 100;
    double mid, midmid;
    double tem1, tem2, output;
    while(hi-lo >= 0.01){
        mid = (lo+hi)/2;
        midmid = (mid+hi)/2;
        tem1 = get_total(x, mid);
        tem2 = get_total(x, midmid);
        if(tem1 > tem2) lo = mid;
        else hi = midmid;
    }

    output = get_total(x, hi);
    if(output < output_dist){
        output_dist = output;
        output_x = x;
        output_y = hi;
    }
    return output;
}

void try_x()
{
    double lo = 0, hi = 100;
    double mid, midmid;
    double tem1, tem2;
    while(hi-lo >= 0.01){
        mid = (lo+hi)/2;
        midmid = (mid+hi)/2;
        tem1 = try_y(mid);
        tem2 = try_y(midmid);
        if(tem1 > tem2) lo = mid;
        else hi = midmid;
    }
}


int main()
{
    freopen("fence3.in", "r", stdin);
    freopen("fence3.out", "w", stdout);

    cin >> F;
    for(int i = 0; i < F; ++i){
        cin >> fence[i].a_x >> fence[i].a_y >> fence[i].b_x >> fence[i].b_y;
    }

    try_x();

    printf("%.1lf %.1lf %.1lf\n", output_x, output_y, output_dist);
    //cout << output_dist << ' ' << output_x << ' ' << output_y << endl;

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值