三分查找专题

我们都知道 二分查找 适用于单调函数中逼近求解某点的值。

如果遇到凸性或凹形函数时,可以用三分查找求那个凸点或凹点。

下面的方法应该是三分查找的一个变形。


如图所示,已知左右端点L、R,要求找到白点的位置。

思路:通过不断缩小 [L,R] 的范围,无限逼近白点。

做法:先取 [L,R] 的中点 mid,再取 [mid,R] 的中点 mmid,通过比较 f(mid) 与 f(mmid) 的大小来缩小范围。

           当最后 L=R-1 时,再比较下这两个点的值,我们就找到了答案。

1、当 f(mid) > f(mmid) 的时候,我们可以断定 mmid 一定在白点的右边。

反证法:假设 mmid 在白点的左边,则 mid 也一定在白点的左边,又由 f(mid) > f(mmid) 可推出 mmid < mid,与已知矛盾,故假设不成立。

所以,此时可以将 R = mmid 来缩小范围。

2、当 f(mid) < f(mmid) 的时候,我们可以断定 mid 一定在白点的左边。

反证法:假设 mid 在白点的右边,则 mmid 也一定在白点的右边,又由 f(mid) < f(mmid) 可推出 mid > mmid,与已知矛盾,故假设不成立。

同理,此时可以将 L = mid 来缩小范围。

算法的正确性:
1、mid与midmid在最值的同一侧。由于凸性函数在最大值(最小值)任意一侧都具有单调性,因此,mid与midmid中,更大(小)的那个 数自然更为靠近最值。此时,我们远离最值的那个区间不可能包含最值,因此可以舍弃。
2、mid与midmid在最值的两侧。由于最值在中间的一个区间,因此我们舍弃一个区间后,并不会影响到最值

int SanFen(int l,int r) //找凸点
{
    while(l < r-1)
    {
        int mid  = (l+r)/2;
        int mmid = (mid+r)/2;
        if( f(mid) > f(mmid) )
            r = mmid;
        else
            l = mid;
    }
    return f(l) > f(r) ? l : r;
}

另一种三分写法

double three_devide(double low,double up)
{
    double m1,m2;
    while(up-low>=eps)
    {
        m1=low+(up-low)/3;
        m2=up-(up-low)/3;
        if(f(m1)<=f(m2))
            low=m1;
        else
            up=m2;
    }
    return (m1+m2)/2;
}

例题:

HDU4717

题意:给N个点,给出N个点的方向和移动速度,求每个时刻N个点中任意两点的最大值中的最小值,以及取最小值的时刻

说明下为啥满足三分:

设y=f(x) (x>0)表示任意两个点的距离随时间x的增长,距离y的变化。则f(x)函数单调性有两种:1.先单减,后单增。2.一直单增。 

设y=m(x) (x>0)表示随时间x的增长,所有点的最大距离y的变化。即m(x)是所有点对构成的f(x)图像取最上面的部分。则m(x)的单调性也只有两种可能:1.先单减,后单增。2.一直单增。 这个地方的证明可以这样:假如时刻t1到时刻t2最大值取得是函数f1(x)的图像,在时刻t2到时刻t3取得是f2(x)的图像,

 

那么由图可以看出f2(x)的斜率大于f1(x)的斜率

可以归纳出m(x)函数的斜率是递增。那么单调性就可以知道了。

m(x)有了上面的性质,就可以有三分了。


#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<cstdio>
using namespace std;
double eps=1e-6;
int n;  //点的个数
struct mq
{
    double x;
    double y;
    double vx;
    double vy;
};
mq node[305];

double dis(mq a,mq b,double t)
{
    return sqrt((a.x+a.vx*t-b.x-b.vx*t)*(a.x+a.vx*t-b.x-b.vx*t)+(a.y+a.vy*t-b.y-b.vy*t)*(a.y+a.vy*t-b.y-b.vy*t));
}

double cal(double t)
{
    int i,j;
    double ans=0;
    for(i=0;i<n;i++)
        for(j=i+1;j<n;j++)
            ans=max(ans,dis(node[i],node[j],t));
    return ans;
}

int main()
{
    int tes,i;
    scanf("%d",&tes);
    int cas=0;
    while(tes--)
    {
        scanf("%d",&n);
        for(i=0;i<n;i++)
            scanf("%lf%lf%lf%lf",&node[i].x,&node[i].y,&node[i].vx,&node[i].vy);

        double left,right,mid,mimid;
        left=0,right=10000000;
        while(right-left>eps)
        {
            mid=(left+right)/2.0,mimid=(right+mid)/2.0;
            if(cal(mid)<cal(mimid))
                right=mimid;
            else
                left=mid;
        }

        printf("Case #%d: %.2f %.2f\n",++cas,mid,cal(mid));
    }

    return 0;
}

POJ3737

题意:给你一个圆锥,圆锥面积给出,问你圆锥的最大体积是多少。

思路:枚举底面圆半径,算圆锥的体积。可以列出表达式,满足三分,因此可以三分枚举底面圆半径。

#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
double s,h;
const double pi=acos(-1.0);
{
    h=sqrt(pow((s-pi*r*r)/(pi*r),2.0)-r*r);
    return pi*r*r*h/3.0;
}

int main()
{
    while(scanf("%lf",&s)!=EOF)
    {
        double left=0.0,right=sqrt(s/(2.0*pi));
        double m,mm;
        while(left+1e-11<right)
        {
            m=(left+right)/2.0;
            mm=(m+right)/2.0;
            if(v(m)>v(mm))right=mm;
            else
                left=m;
        }
        printf("%.2lf\n%.2lf\n%.2lf\n",v(left),h,left);
    }
    return 0;
}


HUD4355
题意:有n个精灵位于一维坐标轴上, 每个精灵有一个权值w, 每个精灵走到另一个位置耗费能量S^3 * w。(s是两点间距离)。现在精灵要聚会, 求选取一点,使所有精灵到这点浪费能量之和最小。

何来证在图上的曲线满足凸性函数可以用来三分.通过证明导数在整个区间内是递增,且最左端导数小于0,最右端大于零,所以与0点必有一交点,极为极值点.

   

证明:

    在任意两个小精灵之间,不愉快值曲线是一个三次函数,这个函数的导数即△由两部分组成,左边的小精灵产生的不愉快和右边小精灵的不愉快.

    左边小精灵的不愉快是增加的,随着x点的右移.因为不愉快值是距离三次成体重,在这里忽略体重.因为体重改变的只是一个常数.所以每个小精灵产生的△是距离的两次.随着x点的右移,左△为正,且增加. 同理 右边的△是负的,因为在向右走的同时,不愉快值在减小,而且与距离成二次比,距离减小,所以总的△在这段之间是递增的.

    同样在每相邻的小精灵之间都是递增的,即在整个区间内是△是递增的. 而很明显最左端以外,△是负的,最右端△是正的,所以这样就证明了导数在整个区间由负到正.是凸性函数.

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>

using namespace std;

#define EPS 1e-7
#define MAXN 50050
double x[MAXN], w[MAXN];
int n;

double Cal(double pos){
    double ans = 0;
    for(int i = 1; i <= n; i ++){
        double tmp = abs(pos - x[i]);
        ans += w[i] * tmp * tmp * tmp;
    }
    return ans;
}

double Solve(){
    double Left, Right;
    double mid, midmid;
    double mid_value, midmid_value;
    Left = x[1];
    Right = x[n];
    while(Left + EPS < Right){
        mid = (Left + Right) / 2;
        midmid = (mid + Right) / 2;
        mid_value = Cal(mid);
        midmid_value = Cal(midmid);
        if(mid_value <= midmid_value)
            Right = midmid;
        else
            Left = mid;
    }
    return Cal(Left);
}

int main(){
    int t;
    int cnt = 0;
    while(~scanf("%d", &t))
    {
         while(t --){
            cnt ++;
            scanf("%d", &n);
            for(int i = 1; i <= n; i ++){
                scanf("%lf %lf", &x[i], &w[i]);
            }
            printf("Case #%d: %.0lf\n", cnt, Solve());
        }
    }

    return 0;
}


HDU3400

题目大意:

给出两条平行的线段AB, CD,然后一个人在线段AB的A点出发,走向D点,其中,人在线段AB上的速度为P, 在线段CD上的速度为Q,在其他地方的速度为R,求人从A点到D点的最短时间。

分析:

典型的三分法,先三分第一条线段,找到一个点,然后根据这个点再三分第二条线段即可

ab传送带要有一个间断点mid1, a<mid1<b(从此点跳出ab这条线)同理cd有个间断mid2,路线是a-->mid1-->mid2-->d;

双向三分过程:

默认 mid1=b

   1.由ab对cd取三分,得到mid2点,返回时间t1

            具体为:a到mid1,mid1到cd之间的L点和R点的最小值点mid(做三分),mid到d点,得到三分过 程的最小t1 将mid点赋值给mid2点

   2.由cd对ab求三分确定mid1点,返回时间t2

             由于此时的d到mid2的时间固定,此时只要三分规划出a到mid,mid到mid2的最小值,将mid的值付给mid1

3.重复1和2直到|t1-t2|无穷小为止

#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cmath>
const double eps = 1e-8;
using namespace std;
struct point
{
    double x,y;
}a,b,c,d;
double v1,v2,v0;
double ax,ay,bx,by,cx,cy,dx,dy;
double dis(point a,point b)
{
    return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
double f(point x)
{
    double t1,t2;
    point l,r,m1,m2;
    l=c,r=d;
   do
    {
        m1.x=(l.x+r.x)/2;
        m1.y=(l.y+r.y)/2;
        m2.x=(m1.x+r.x)/2;
        m2.y=(m1.y+r.y)/2;
        t1=dis(m1,x)/v0+dis(m1,d)/v2;
        t2=dis(m2,x)/v0+dis(m2,d)/v2;
        if(t1>t2) l=m1;
        else r=m2;
    }while(dis(l,r)>=eps);
    return t1;
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        cin>>a.x>>a.y>>b.x>>b.y>>c.x>>c.y>>d.x>>d.y;
        cin>>v1>>v2>>v0;
        point l,r,m1,m2;
        double t1,t2;
        l=a;r=b;
        do
        {
            m1.x=(l.x+r.x)/2;
            m1.y=(l.y+r.y)/2;
            m2.x=(m1.x+r.x)/2;
            m2.y=(m1.y+r.y)/2;
            t1=dis(m1,a)/v1+f(m1);
            t2=dis(m2,a)/v1+f(m2);
            if(t1>t2) l=m1;
            else r=m2;
        }while(dis(l,r)>=eps);
        printf("%.2lf\n",t1);
    }
    return 0;
}

HUD2438

其实就是汽车能不能拐弯的问题,告诉你X, Y, l, d判断是否能够拐弯,

做题感悟:做完这题发现三分一定与数学知识相结合出现,而且不是简单的数学知识。

解题思路:关键是要找到小车的运动状态,下面是分析和公式推导。

在小车转弯过程中,黄线是不断地变化的,变化规律是先增大再减小(可以自己模拟一下,只要黄线最大值小于y,车就可以通过)。这样可以得到一个关于图中角度的公式,不断三分角度就可以得到最大的黄线长度了(采用三分是因为黄线所形成的函数是凸函数),角度(0 , PI / 2.0)。

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <string>
#include <cmath>
using namespace std;
double L,x,d,y;
const double PI = acos(-1.0);
double ans(double sita)
{
    return L*sin(sita) -x*tan(sita)+d/cos(sita);
}

int main()
{

    while(cin>>x>>y>>L>>d)
    {
        double l = 0, r = PI/2;
        while(r-l>=1e-8)
        {
            double mid = (r+l)/2;
            double midmid = (mid + r)/2;
            if(ans(mid) > ans(midmid)) r = midmid;
            else l = mid;
        }
        if(y -ans(r) > 1e-8) cout<<"yes"<<endl;
        else cout<<"no"<<endl;
    }
    return 0;
}


1221: 高考签到题

Time Limit: 1 Sec   Memory Limit: 128 MB
Submit: 35   Solved: 13
[ Submit][ Status][ Web Board]

Description


在直角坐标系中有一条抛物线y=ax^2+bx+c和一个点P(x,y),求点P到抛物线的最短距离d。

Input

多组数据。

5个整数a,b,c,x,y。前三个数构成抛物线的参数,后两个数x,y表示P点坐标。-200≤a,b,c,x,y≤200

Output

1个实数d,保留3位小数(四舍五入)

Sample Input

2 8 2 -2 6

Sample Output

2.437

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <set>
#include <cmath>
using namespace std;
const int INF = 9999990;

double dis(double x, double y, double xx , double yy )
{
    return (xx - x)*(xx - x) + (yy - y)*(yy - y);
}

int main()
{
    #ifdef xxz
    freopen("in.txt","r",stdin);
    #endif // xxz
    double a,b,c,x,y;

    while(cin>>a>>b>>c>>x>>y){
        double L ,R;
        if(x < -b*1.0/(2*a)){
            L = -200.0;
            R = -b*1.0/(2*a);
        }else {
            L = -b*1.0/(2*a) - 1;
            R = 200.0;
        }

        for(int i = 0; i < 100 ;i ++){
            double mid = (L + R)/2.0;
            double mmid = (mid + R)/2.0;
            double dis1 = dis(mid,a*mid*mid + b*mid + c, x , y);
            double dis2 = dis(mmid,a*mmid*mmid + b*mmid + c, x , y);
            if(dis1 < dis2 ){
                R = mmid;
            }
            else L = mid;
        }

        printf("%.3lf\n",sqrt(dis(L,a*L*L + b*L + c, x , y)))  ;

    }
    return 0;
}



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值