实数三分—三分求极值、Line belt

三分求极值

题目是hihocode第1142道题,貌似现在已经无法注册了,我尝试了多次均已失败告终。
废话不多说,直接上题目

Problem Description

在直角坐标系中有一条抛物线y=ax^2+bx+c和一个点P(x,y),求点P到抛物线的最短距离d。

Input

第1行:5个整数a,b,c,x,y。前三个数构成抛物线的参数,后两个数x,y表示P点坐标。-200≤a,b,c,x,y≤200

Output

第1行:1个实数d,保留3位小数(四舍五入)

Sample Input

2 8 2 -2 6

Sample Output

2.437

分析:

看到求最短距离,忽然回想起熟悉的两点间距离公式:d=sqrt((X-x)^2 + (Y-y)^2)
所以只要求得最小d,就求出答案。因为需要输入x,y 所以我们对公式化简,很容易发现,公式化简后类似一个二次函数,所以把求最短距离问题——>求凸函数极值问题,于是乎开始实数三分法,代码如下。


代码实现

# include <cstdio>
# include <iostream>
# include <cmath>
using namespace std;
double a,b,c,X,Y;
double possible( double x )
{
    double y = a*x*x+b*x+c;
    double res = hypot(X-x,Y-y);
    return res;
}
int main(void)
{
    cin>>a>>b>>c>>X>>Y;
    double l = -233.0;
    double r = 233.0;
    while ( abs(r-l) > 1e-8 )
    {
            double m1 = l+(r-l)/3;
            double m2 = r-(r-l)/3;
            if ( possible(m1) < possible(m2) )
            {
                r = m2;
            }
            else
            {
                l = m1;
            }
    }
    printf("%.3lf\n",(possible(l)+(possible(r)))/2);


    return 0;
}
注:代码第9行hypot() 函数是 cmath 头文件的库函数,用于求给定数字的斜边,它接受两个数字并返回斜边的计算结果,即sqrt(xx + yy)。

Line Belt

Problem Description

In a two-dimensional plane there are two line belts, there are two segments AB and CD, lxhgww's speed on AB is P and on CD is Q, he can move with the speed R on other area on the plane.
How long must he take to travel from A to D?

大意:

平面上有两条轨道,AB和CD,轨道上可以坐车,从A到B的速度是P,而从C到D的速度是Q,不在AB或CD上就只有步行了,速度是R。现在问从A到D的最短时间

Input

The first line is the case number T.
For each case, there are three lines.
The first line, four integers, the coordinates of A and B: Ax Ay Bx By.
The second line , four integers, the coordinates of C and D:Cx Cy Dx Dy.
The third line, three integers, P Q R.
0<= Ax,Ay,Bx,By,Cx,Cy,Dx,Dy<=1000
1<=P,Q,R<=10

Output

The minimum time to travel from A to D, round to two decimals.

Sample Input

1
0 0 0 100
100 0 100 100
2 2 1

Sample Output

136.60

Limit

Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)


分析:

如图所示画的好潦草,所以这道题的的做法是显而易见的就是,在AB处取一点E,CD处取一点F,让TAE+TEF+TFD取得最小,这时候发动我们的单核草履虫的大脑,如果让A直接到D可能距离最短,但是万一速度比较慢,也会使得总时间不是最短,所以需要一段AE和一段FD去分担一部分时间,但是AE与FD的距离又不能太长,长了也会使总时间加大的,所以这时我们分析E点取值,假设F点的取值已经确定,那么TAE+TEF应该随着AE在AB所占比的增大而先减小后增大,所以存在一个最小值点,属于单谷函数同理对于点F,也假设E点已知,TFD+TEF也是单谷函数,单谷函数求极值——三分法,这里有两个三分又互相影响,所以就是一个三分嵌套问题,代码实现如下:


代码实现

#include <iostream>
#include <cmath>
#include <algorithm>
#include <cstdio>
using namespace std;

#define EPS 1e-6

struct Point {
    double x;
    double y;
};

Point a, b, c, d, e, f;
double p, q, r;

double dis(Point p1, Point p2) {
    return sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y) );
}

double ae_cost(double alpha) {//alpha是某点在某线段内所占的比例
    e.x = a.x + (b.x - a.x) * alpha;
    e.y = a.y + (b.y - a.y) * alpha;
    return dis(a, e) / p+dis(e, f) / r;
}

double all_cost(double alpha) {
    f.x = c.x + (d.x - c.x) * alpha;
    f.y = c.y + (d.y - c.y) * alpha;
    double time =-1 ;
    double L = 0.0, R = 1.0;
    while (R - L > EPS) {
        double mid = (R - L) / 3.0;
        double alpha1 = L + mid, alpha2 = R - mid;
        if (ae_cost(alpha1) - ae_cost(alpha2)>EPS) {
            L = alpha1;
        } else {
           R = alpha2;
        }
  
    }
    time = dis(f, d) / q ;
    return ae_cost(L) + time;
}

int main() {
    int t;
    cin >> t;
    while (t--) {
        cin >> a.x >> a.y >> b.x >> b.y;
        cin >> c.x >> c.y >> d.x >> d.y;
        cin >> p >> q >> r;
        double L = 0.0, R = 1.0;
        while (R - L > EPS) {
            double mid = (R - L) / 3.0;
            double alpha1 = L + mid, alpha2 = R - mid;
            if (all_cost(alpha1) - all_cost(alpha2)>EPS) {
                L = alpha1;
            } else {
                R = alpha2;
            }
    
        }
        printf("%.2lf\n", all_cost(L));
    }
    return 0;
}
吐苦水🤮

这道题代码改了好久,本来写好的运行整数一点问题没有,后来提交hduoj评测怎么也过不去,然后就是自己疯狂试,终于发现如果输入坐标是小数,答案就会出错,
错误代码:

#include <iostream>
#include <cmath>
#include <algorithm>
#include <cstdio>
using namespace std;

#define EPS 1e-12

struct Point {
    double x;
    double y;
};

Point a, b, c, d, e, f,ans;
double p, q, r;

double dis(Point p1, Point p2) {
    return sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y) + 1e-9);
}

double ae_cost(double alpha) {
    e.x = a.x + (b.x - a.x) * alpha;
    e.y = a.y + (b.y - a.y) * alpha;
    return dis(a, e) / p;
}

double all_cost(double alpha) {
    f.x = c.x + (d.x - c.x) * alpha;
    f.y = c.y + (d.y - c.y) * alpha;
    double time = dis(f, d) / q + dis(e, f) / r;
    double L = 0.0, R = 1.0;
    while (R - L > EPS) {
        double mid = (R - L) / 3.0;
        double alpha1 = L + mid, alpha2 = R - mid;
        if (ae_cost(alpha1) < ae_cost(alpha2)) {
            R = alpha2;
        } else {
            L = alpha1;
        }
        ans.x=L;
    }
    return ae_cost(L) + time;
}

int main() {
    int t;
    cin >> t;
    while (t--) {
        cin >> a.x >> a.y >> b.x >> b.y;
        cin >> c.x >> c.y >> d.x >> d.y;
        cin >> p >> q >> r;
        double L = 0.0, R = 1.0;
        while (R - L > EPS) {
            double mid = (R - L) / 3.0;
            double alpha1 = L + mid, alpha2 = R - mid;
            if (all_cost(alpha1) < all_cost(alpha2)) {
                R = alpha2;
            } else {
                L = alpha1;
            }
            ans.y=L;
        }
        printf("%.2lf\n", all_cost(L));
        cout<<ans.x<<" "<<ans.y;
    }
    return 0;
}

可以看到,这里分成AE和EF+FD两部分了,这样形成的一个错误就是TAE是单增的随着E的增大,不适用三分法,而且这样求出来的E就是A点了,Tall就是一个根据错误的局部答案推断的综合错误答案,该答案Tall=TAF+TFD,只对这一部分进行三分,ok现在问题迎刃而解,至于为什么输入整数不会错,那是因为输入的不具普遍性正中错误的点😄

posted @ 2023-07-19 01:21  LongDz  阅读(18)  评论(0)    收藏  举报