类似于二分查找,三分搜索法也是比较常用的基于分治思想的高效查找方法。但是和二分不同,二分只适用于单调函数,比如常用的对单调递增或单调递减的一个序列中的某一个元素进行查找,三分却突破了这种限制,可以用于左边递增右边递减或者相反的,这么一类函数,也就是常说的凸函数和凹函数。但是为什么三分法可以用于凸函数或者凹函数呐,这其实是因为这种函数总是有一个最大值或者最小值,这样就可以借此判断出三分法中两个中点相对相对于极值的位置,例如下图(凹函数类似):
三分搜索的实现主要是判断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
How long must he take to travel from A to D?
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
1 0 0 0 100 100 0 100 100 2 2 1
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。