1. 程式人生 > >ACM-三分搜尋

ACM-三分搜尋

類似於二分查詢,三分搜尋法也是比較常用的基於分治思想的高效查詢方法。但是和二分不同,二分只適用於單調函式,比如常用的對單調遞增或單調遞減的一個序列中的某一個元素進行查詢,三分卻突破了這種限制,可以用於左邊遞增右邊遞減或者相反的,這麼一類函式,也就是常說的凸函式和凹函式。但是為什麼三分法可以用於凸函式或者凹函式吶,這其實是因為這種函式總是有一個最大值或者最小值,這樣就可以藉此判斷出三分法中兩個中點相對相對於極值的位置,例如下圖(凹函式類似):


三分搜尋的實現主要是判斷midl和midr所在值的大小。以凸函式為例(凹函式類似,只是判mid大小的時候保留小的即可(其實也是保留離極值最近的mid)),先以left和right為端點計算出它們的中點midl,然後再以midl和right為端點計算出它們的中點midr,接下來就需要判斷f(midl)和f(midr)值的大小了,如果f(midl)大於f(midr),那麼說明midl靠近極值,此時令right=midr,否則說明midr靠近極值,此時則令left=midl,總之就是要保留離極值最近的那一個mid,然後重複前面的過程,直到left和right十分接近,最終f(left)就等於了極值,下面給出程式實現:

double solve(double parameter)
{
    // 計算函式值,即f(x)
}

double trisection_search(double left, double right)
{
    // 三分搜尋,找到最優解(求函式最大值下的自變數值)
    double midl, midr;
    while (right-left > 1e-7)
    {
        midl = (left + right) / 2;
        midr = (midl + right) / 2;
        // 如果是求最小值的話這裡判<=即可
        if(solve(midl) >= solve(midr)) right = midr;
        else left = midl;
    }
    return left;
}

下面以一道例題來練習,HDOJ(3400),時空轉移(點選開啟連結),題目如下:

Line belt

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 3036    Accepted Submission(s): 1163


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?
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 題意:

有兩條傳送帶,傳送帶上有兩段,AB和CD,它們的速度分別是P和Q,其它地方的速度為R,問從A到D所需要的最短時間是多少,此問題的一般過程如下圖:


分析:

試想如果求AB上固定一點X到CD的距離,那麼這樣的函式關係一定是一個凹函式,因為一定有一個最短距離,然後向CD兩端延伸距離都是越來越大的。所以,求解凹函式的極值問題,三分法首當其衝,先使用三分列舉AB段上的最優解,針對列舉的每一個值,再使用三分法列舉CD段上的最優解,這樣三分巢狀三分最後就可以得到最終的最優解。

原始碼:

#include <cstdio>
#include <cmath>
#include <algorithm>

#define eps 1e-7
using namespace std;

struct Point
{
    double x;
    double y;
}a, b, c, d, X, Y;
double p, q, r;
double ab, cd;

// 計算兩點間距離
double dist(Point &a1, Point &a2)
{
    // 加eps,可能測試資料都是int型別開方有誤差
    double x = (a2.x-a1.x) * (a2.x-a1.x);
    double y = (a2.y-a1.y) * (a2.y-a1.y);
    return sqrt(x + y + eps);
}

// 計算XY、YD所用時間
double solve(double cy)
{
    // 計算Y點座標,按比例計算即可,注意(d.x-c.x)正負
    Y.x = c.x + (d.x-c.x)*(cy/cd);
    Y.y = c.y + (d.y-c.y)*(cy/cd);
    return dist(X, Y)/r + (cd-cy)/q;
}

// 對cd進行三分,並計算出總時間
double trisection_search_cd(double ax)
{
    // 計算X點座標,按比例計算即可,注意(b.x-a.x)正負
    X.x = a.x + (b.x-a.x)*(ax/ab);
    X.y = a.y + (b.y-a.y)*(ax/ab);

    double ans, tmp1, tmp2;
    double left=0, right=cd;
    double midl, midr;
    while(right-left > eps)
    {
        midl = (left+right) / 2;
        midr = (midl+right) / 2;

        if((tmp1=solve(midl)) <=
           (tmp2=solve(midr))) right = midr;
        else left = midl;
        ans = min(tmp1, tmp2);
    }
    return ax/p + ans;
}

// 對ab進行三分
double trisection_search_ab(double left, double right)
{
    double ans, tmp1, tmp2;
    double midl, midr;
    while(right-left > 1e-7)
    {
        midl = (left+right) / 2;
        midr = (midl+right) / 2;

        if((tmp1=trisection_search_cd(midl)) <=
            (tmp2=trisection_search_cd(midr))) right = midr;
        else left = midl;
        ans = min(tmp1, tmp2);
    }
    return ans;
}

int main()
{//freopen("sample.txt", "r", stdin);
    int cas;
    scanf("%d", &cas);
    while(cas--)
    {
        scanf("%lf%lf%lf%lf", &a.x, &a.y,
              &b.x, &b.y);
        scanf("%lf%lf%lf%lf", &c.x, &c.y,
              &d.x, &d.y);
        scanf("%lf%lf%lf", &p, &q, &r);

        ab = dist(a, b);
        cd = dist(c, d);
        double ans = trisection_search_ab(0, ab);

        printf("%.2f\n", ans);
    }
    return 0;
}

其它類似的題目還有,HDOJ:2438,ZOJ:3203,POJ:3301。