三分搜索
二分法作为分治中最常见的方法,适用于单调函数,逼近求解某点的值。但当函数是凸性函数时,二分法就无法适用,这时三分法就可以“大显身手”
如图,类似二分的定义Left和Right,mid = (Left + Right) / 2,midmid = (mid + Right) / 2; 如果mid靠近极值点,则Right = midmid;否则(即midmid靠近极值点),则Left = mid; 程序模版如下:
1 double Calc(Type a)
2 {
3 /* 根据题目的意思计算 */
4 }
5 void Solve(void)
6 {
7 double Left, Right;
8 double mid, midmid;
9 double mid_value, midmid_value;
10 Left = MIN; Right = MAX;
11 while (Left + EPS < Right)
12 {
13 mid = (Left + Right) / 2;
14 midmid = (mid + Right) / 2;
15 mid_area = Calc(mid);
16 midmid_area = Calc(midmid); // 假设求解最大极值.
17 if (mid_area >= midmid_area)
18 Right = midmid;
19 else
20 Left = mid;
21 }
22 }
ZOJ 3203 Light Bulb
http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3203
如图,人左右走动,求影子L的最长长度。 根据图,很容易发现当灯,人的头部和墙角成一条直线时(假设此时人站在A点),此时的长度是影子全在地上的最长长度。当人再向右走时,影子开始投影到墙上,当人贴着墙,影子长度即为人的高度。所以当人从A点走到墙,函数是先递增再递减,为凸性函数,所以我们可以用三分法来求解。 下面只给出Calc函数,其他直接套模版即可。
double Calc(double x)
{ return (h * D - H * x) / (D - x) + x; }
杭电2438 Turn the corner
http://acm.hdu.edu.cn/showproblem.php?pid=2438
汽车拐弯问题,给定X, Y, l, d判断是否能够拐弯。首先当X或者Y小于d,那么一定不能。 其次我们发现随着角度θ的增大,最大高度h先增长后减小,即为凸性函数,可以用三分法来求解。
这里的Calc函数需要比较繁琐的推倒公式:
s = l * cos(θ) + w * sin(θ) - x;
h = s * tan(θ) + w * cos(θ);
其中s为汽车最右边的点离拐角的水平距离, h为里拐点最高的距离, θ范围从0到90。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <iostream>
2 #include <stdio.h>
3 #include <string.h>
4 #include <math.h>
5 using namespace std;
6 const double pi=acos(-1.0);
7 double x,y,l,w;
8
9 double cal(double a)
10 {
11 double s=l*cos(a)+w*sin(a)-x;
12 double h=s*tan(a)+w*cos(a);
13 return h;
14 }
15 int main()
16 {
17 while(cin>>x>>y>>l>>w)
18 {
19 if(x<w||y<w)
20 printf("no\n");
21 else
22 {
23 double l=0,h=pi/2,m,mm;
24 while(h-l>1e-9)
25 {
26 m=(h+l)/2;
27 mm=(h+m)/2;
28 if(cal(m)>cal(mm))
29 h=mm;
30 else
31 l=m;
32 }
33 if(cal((m+mm)/2)<=y)
34 printf("yes\n");
35 else
36 printf("no\n");
37 }
38 }
39 return 0;
40 }
POJ 3301 Texas Trip
http://acm.pku.edu.cn/JudgeOnline/problem?id=3301
题意为给定n(n <= 30)个点,求出饱含这些点的面积最小的正方形。
有两种解法,一种为逼近法,就是每次m分角度,求出最符合的角度,再继续m分,如此进行times次,即可求出较为精确的解。(m 大概取10, times取30即可)
第二种解法即为三分法,首先旋转的角度只要在0到180度即可,超过180度跟前面的相同的。坐标轴旋转后,坐标变换为:
X’ = x * cosa - y * sina; y’ = y * cosa + x * sina;
至于这题的函数是否是凸性的,为什么是凸性的,我也无法给出准确的证明,希望哪位路过的大牛指点一下~~
例题更新(2010.5.5)
hdu 3400 Line belt
http://acm.hdu.edu.cn/showproblem.php?pid=3400
典型的三分法,先三分第一条线段,找到一个点,然后根据这个点再三分第二条线段即可,想出三分的思路基本就可以过了。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 /*
2 所示必定在AB CD上存在XY 使得A---D 用时间最短。
3 用三分法针对AB间的某个点找出在CD段上最小的点,
4 嵌套循环找出最小的时间。
5 有关时间函数在AB段和在CD段均为凸函数,所以可以用三分法。
6 */
7 #include <iostream>
8 #include <string.h>
9 #include <stdlib.h>
10 #include <stdio.h>
11 #include <math.h>
12 using namespace std;
13 int q,p,r;
14 typedef struct {
15 double x,y;
16 }point;
17
18 double dis(point a,point b)
19 {
20 return pow(pow(a.x-b.x,2.0)+pow(a.y-b.y,2.0),1.0/2);
21 }
22 point mid(point l,point h)
23 {
24 point m;
25 m.y=(l.y+h.y)/2;
26 m.x=(l.x+h.x)/2;
27 return m;
28 }
29 //对于AB见任意一点y找到到d点所用最短时间
30 double time(point c,point d,point y)
31 {
32 point m,mm;
33 point l=c,h=d;
34 double t1,t2;
35 do
36 {
37 m=mid(l,h);
38 mm=mid(m,h);
39 t1=dis(m,y)/r+dis(m,d)/q;
40 t2=dis(mm,y)/r+dis(mm,d)/q;
41 if(t1>t2)
42 l=m;
43 else
44 h=mm;
45 }while(fabs(t1-t2)>1e-9);
46 return (t1+t2)/2;
47 }
48 int main()
49 {
50 int t;
51 point a,b,c,d;
52 while(cin>>t)
53 {
54 while(t--)
55 {
56 cin>>a.x>>a.y>>b.x>>b.y;
57 cin>>c.x>>c.y>>d.x>>d.y;
58 cin>>p>>q>>r;
59 point l=a,h=b;
60 point m,mm;
61 double t1,t2;
62 do{
63 m=mid(l,h);
64 mm=mid(m,h);
65 t1=dis(a,m)/p+time(c,d,m);
66 t2=dis(a,mm)/p+time(c,d,mm);
67 if(t1>t2)
68 l=m;
69 else
70 h=mm;
71
72 }while(fabs(t1-t2)>1e-9);
73 printf("%.2lf\n",(t1+t2)/2);
74 }
75 }
76 return 0;
77 }
hdu2899 Strange fuction
http://acm.hdu.edu.cn/showproblem.php?pid=2899
对函数 F(x) = 6 * x^7+8*x^6+7*x^3+5*x^2-y*x (0 <= x <=100) 对每个给定的y求最小值,可三分也可二分。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 /*
2 区间单调情况下直接求极值
3 先降后升时可以对原函数3分,也可以对导函数二分求导函数为0的位置
4 */
5 #include <iostream>
6 #include <stdio.h>
7 #include <math.h>
8 using namespace std;
9
10 double f(double x,double y)
11 {
12 return 6*pow(x,7)+8*pow(x,6)+7*pow(x,3)+5*pow(x,2)-y*x;
13 }
14 double func(double x,double y)
15 {
16 return 42*pow(x,6)+48*pow(x,5)+21*pow(x,2)+10*x-y;
17 }
18 int main()
19 {
20 int t;
21 double y;
22 scanf("%d",&t);
23 while(t--)
24 {
25 scanf("%lf",&y);
26 if(func(0,y)>=0)
27 {
28 printf("%.4lf\n",f(0,y));
29 continue;
30 }
31 if(func(100,y)<=0)
32 {
33 printf("%.4lf\n",f(100,y));
34 continue;
35 }
36 double l=0,h=100,m,n;
37 while(l<=h)//导函数为0时的值为极值
38 {
39 m=(l+h)/2;
40 n=func(m,y);
41 if(n-0>=1e-4)
42 {
43 h=m;
44 continue;
45 }
46 if(0-n>=1e-4)
47 {
48 l=m;
49 continue;
50 }
51 break;
52 }
53 printf("%.4lf\n",f(m,y));
54 }
55 return 0;
56 }
hdu2298 Toxophily
http://acm.hdu.edu.cn/showproblem.php?pid=2298
由(0,0)射出的弓箭击中(x,y)点的最小角度,若不能输出-1.
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 /*
2 x=v*cosα*t;
3 y=v*sinα*t-1/2*g*t^2;
4 化简消去t得 :y=x*tanα-x^2*g/(2*v^2*cosα*cosα).
5 三分+二分
6 可知该方程先递增后递减,所以先三分求出最大值,
7 如果y小于最大值,则输出-1,否则二分求出α值。
8 */
9 #include <stdio.h>
10 #include <string.h>
11 #include <math.h>
12 const double pi=acos(-1.0),g=9.8;
13 double x,y,v;
14 double fu(double a)
15 {
16 return x*tan(a)-(g*x*x)/(2*v*v*cos(a)*cos(a));
17 }
18 double sanfen(double l,double h)//三分求最值
19 {
20 double m,mm;
21 while(h-l>1e-9)
22 {
23 m=(l+h)/2;
24 mm=(m+h)/2;
25 if(fu(m)>fu(mm))
26 h=mm;
27 else
28 l=m;
29 }
30 return (m+mm)/2;
31 }
32 double erfen(double l,double h)//已经求得最大值,在l到h间是单调
33 {
34 double m;
35 while((h-l)>1e-9)
36 {
37 m=(l+h)/2;
38 if(fu(m)>y)
39 h=m;
40 else
41 l=m;
42 }
43 return m;
44 }
45 int main()
46 {
47 int t;
48 scanf("%d",&t);
49 while(t--)
50 {
51 scanf("%lf%lf%lf",&x,&y,&v);
52 double h=sanfen(0,pi/2);
53 if(fu(h)<y)
54 printf("-1\n");
55 else
56 printf("%.6lf\n",erfen(0,h));
57 }
58 return 0;
59 }
对于求解一些实际问题,当公式难以推导出来时,二分、三分法可以较为精确地求解出一些临界值,且效率也是令人满意的。(转自http://hi.baidu.com/czyuan_acm/blog/item/8cc45b1f30cefefde1fe0b7e.html)