[转]三分法——求解凸性函数的极值问题——czyuan原创

2009年05月26日 星期二 23:40

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


       如图,类似二分的定义Left和Right,mid = (Left + Right) / 2,midmid = (mid + Right) / 2; 如果mid靠近极值点,则Right = midmid;否则(即midmid靠近极值点),则Left = mid;

程序模版如下:

double Calc(Type a)
{
    /* 根据题目的意思计算 */
}

void Solve(void)
{
    double Left, Right;
    double mid, midmid;
    double mid_value, midmid_value;
    Left = MIN; Right = MAX;
    while (Left + EPS < Right)
    {
        mid = (Left + Right) / 2;
        midmid = (mid + Right) / 2;
        mid_area = Calc(mid);
        midmid_area = Calc(midmid);
        // 假设求解最大极值.
        if (mid_area >= midmid_area) Right = midmid;
        else Left = mid;
    }
}

现根据几道的OJ题目来分析三分法的具体实现。

buaa 1033 Easy Problem
http://acm.buaa.edu.cn/oj/problem_show.php?c=0&p=1033

题意为在一条线段上找到一点,与给定的P点距离最小。很明显的凸性函数,用三分法来解决。
Calc函数即为求某点到P点的距离。

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;
}

heru 5081 Turn the corner 08年哈尔滨regional网络赛
http://acm.hrbeu.edu.cn/index.php?act=problem&id=1280


汽车拐弯问题,给定X, Y, l, d判断是否能够拐弯。首先当X或者Y小于d,那么一定不能。
其次我们发现随着角度θ的增大,最大高度h先增长后减小,即为凸性函数,可以用三分法来求解。

这里的Calc函数需要比较繁琐的推倒公式:
s = l * cos(θ) + w * sin(θ) - x;
h = s * tan(θ) + w * cos(θ);
其中s为汽车最右边的点离拐角的水平距离, h为里拐点最高的距离, θ范围从0到90。

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

对于求解一些实际问题,当公式难以推导出来时,二分、三分法可以较为精确地求解出一些临界值,且效率也是令人满意的。

/* czyuan原创,转载请注明出处。*/
顺便附上咱的hdu 3400代码
#include using namespace std; #include #include #include #define eps 1e-8 typedef struct { double x,y; }node; node t[4]; double p,q,r; inline double Dist(node a,node b) { double y=(a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y); return sqrt(y+eps); } double __get(double r1,double r2) { node l,re; l.x=t[0].x+(t[1].x-t[0].x)*r1; l.y=t[0].y+(t[1].y-t[0].y)*r1; re.x=t[2].x+(t[3].x-t[2].x)*(1-r2); re.y=t[2].y+(t[3].y-t[2].y)*(1-r2); return Dist(l,t[0])/p+Dist(l,re)/r+Dist(re,t[3])/q; } double _get(double r) { double left=0,right=1; while(left+eps__get(mmid,r)) left=mid; else right=mmid; } return __get(left,r); } int main() { //freopen("input","r",stdin); // freopen("output","w",stdout); int T; cin>>T; while(T--) { for(int i=0;i<4;i++) scanf("%lf %lf",&t[i].x,&t[i].y); scanf("%lf %lf %lf",&p,&q,&r); double left=0,right=1; while(left+eps_get(mmid)) left=mid; else right=mmid; } printf("%.2lf/n",_get(left)); } return 1; }

你可能感兴趣的:(几何)