三分搜索

二分法作为分治中最常见的方法,适用于单调函数,逼近求解某点的值。但当函数是凸性函数时,二分法就无法适用,这时三分法就可以大显身手

 

 如图,类似二分的定义LeftRightmid = (Left + Right) / 2midmid = (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为里拐点最高的距离θ范围从090

 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 }
View Code

 

POJ 3301 Texas Trip

http://acm.pku.edu.cn/JudgeOnline/problem?id=3301

题意为给定n(n <= 30)个点,求出饱含这些点的面积最小的正方形。

有两种解法,一种为逼近法,就是每次m分角度,求出最符合的角度,再继续m分,如此进行times次,即可求出较为精确的解。(m 大概取10, times30即可)

第二种解法即为三分法,首先旋转的角度只要在0180度即可,超过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

典型的三分法,先三分第一条线段,找到一个点,然后根据这个点再三分第二条线段即可,想出三分的思路基本就可以过了。

 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 }
View Code

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求最小值,可三分也可二分。

 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 }
View Code

hdu2298 Toxophily

http://acm.hdu.edu.cn/showproblem.php?pid=2298

由(0,0)射出的弓箭击中(x,y)点的最小角度,若不能输出-1.

 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 }
View Code

 

对于求解一些实际问题,当公式难以推导出来时,二分、三分法可以较为精确地求解出一些临界值,且效率也是令人满意的。(转自http://hi.baidu.com/czyuan_acm/blog/item/8cc45b1f30cefefde1fe0b7e.html

posted on 2013-05-30 16:09  行者1992  阅读(231)  评论(0编辑  收藏  举报