1. 程式人生 > >三分演算法 —(解決凸凹函式的最值)

三分演算法 —(解決凸凹函式的最值)

我們都知道 二分查詢 適用於單調函式中逼近求解某點的值。

如果遇到凸性或凹形函式時,可以用三分查詢求那個凸點或凹點。

下面的方法應該是三分查詢的一個變形。

如圖所示,已知左右端點L、R,要求找到白點的位置。

思路:通過不斷縮小 [L,R] 的範圍,無限逼近白點。

做法:先取 [L,R] 的中點 mid,再取 [mid,R] 的中點 mmid,通過比較 f(mid) 與 f(mmid) 的大小來縮小範圍。

           當最後 L=R-1 時,再比較下這兩個點的值,我們就找到了答案。

1、當 f(mid) > f(mmid) 的時候,我們可以斷定 mmid 一定在白點的右邊。

反證法:假設 mmid 在白點的左邊,則 mid 也一定在白點的左邊,又由 f(mid) > f(mmid) 可推出 mmid < mid,與已知矛盾,故假設不成立。

所以,此時可以將 R = mmid 來縮小範圍。

2、當 f(mid) < f(mmid) 的時候,我們可以斷定 mid 一定在白點的左邊。

反證法:假設 mid 在白點的右邊,則 mmid 也一定在白點的右邊,又由 f(mid) < f(mmid) 可推出 mid > mmid,與已知矛盾,故假設不成立。

同理,此時可以將 L = mid 來縮小範圍。

凸函式最值:

double three_divi(double l,double r)
{
    double mid,mmid;
    while(r-l>=eps)
    {
        mid = (l+r)/2;
        mmid = (mid+r)/2;
        if(cal(mid)>cal(mmid))   //比較函式值
            r = mmid;
        else
            l = mid;
    }
    return cal(l) > cal(r) ? l : r;
}

凹函式最值:

double three_divi(double l,double r)
{
    double mid,mmid;
    while(r-l>=eps)
    {
        mid = (l+r)/2;
        mmid = (mid+r)/2;
        if(cal(mid)<cal(mmid))  //大於改成小於
            r = mmid;
        else
            l = mid;
    }
    return cal(l) > cal(r) ? l : r;
}

不難推出公式:(據說根據扔東西嘗試這是凸函式)

AC程式碼:

#include<iostream>
#include<cstdio>
#include<cmath>
#define pi acos(-1.0)
#define g 9.80665
using namespace std;
const double eps = 1e-8;
double h,v;
inline double cal(double x)
{
    return v*v*sin(2*x)/(2*g)+v*cos(x)*sqrt(v*v*sin(x)*sin(x)+2*g*h)/g;
}
double three_divi(double l,double r)
{
    double mid,mmid;
    while(r-l>=eps)
    {
        mid = (l+r)/2;
        mmid = (mid+r)/2;
        if(cal(mid)>cal(mmid))
            r = mmid;
        else
            l = mid;
    }
    return cal(l) > cal(r) ? l : r;
}
int main()
{
    int t;
    cin>>t;
    while(t--)
    {
        cin>>h>>v;
        double x = three_divi(0.0,pi/2.0); //角度取值0~pi/2
        printf("%.5f\n",cal(x));
    }
    return 0;
}

(ps:物理解法看上一篇 )