二分、三分小结(ZOJ3203 HDU2438 HDU4717 HDU2199 POJ3737 HDU4355 HDU3400 HDU4004)

来源:互联网 发布:网络哔哔是什么意思 编辑:程序博客网 时间:2024/05/23 17:42

总述:

能用二分求解的前提是:一定区间内为单调(递增/递减)的,故二分一般用来查找某个值或解线性方程。

能用三分求解的前提是:一定区间内为有凹凸性的,故三分一般用来求极值。



ZOJ    3203  Light  Bulb(数学公式直接推导 或 三分)

转自:kuangbin点击打开链接


ZOJ Problem Set - 3203
Light Bulb

Time Limit:1 Second     Memory Limit:32768 KB

Compared to wildleopard's wealthiness, his brother mildleopard is rather poor. His house is narrow and he has only one light bulb in his house. Every night, he is wandering in his incommodious house, thinking of how to earn more money. One day, he found that the length of his shadow was changing from time to time while walking between the light bulb and the wall of his house. A sudden thought ran through his mind and he wanted to know the maximum length of his shadow.

Input

The first line of the input contains an integerT (T <= 100), indicating the number of cases.

Each test case contains three real numbersH,h andD in one line.H is the height of the light bulb whileh is the height of mildleopard.D is distance between the light bulb and the wall. All numbers are in range from 10-2 to 103, both inclusive, andH -h >= 10-2.

Output

For each test case, output the maximum length of mildleopard's shadow in one line, accurate up to three decimal places..

Sample Input

32 1 0.52 0.5 34 3 4

 

Sample Output

1.000
0.7504.000
本来此题直接推出了公式,直接找到了最值点的。
但是看到网上好多用三分法做的,就复习了下三分法,因为是凸的,所以用三分。
 
 
公式法:

#include<stdio.h>#include<string.h>#include<math.h>#include<iostream>#include<algorithm>using namespace std;int main(){   // freopen("in.txt","r",stdin);   // freopen("out.txt","w",stdout);    int T;    double H,h,D;    scanf("%d",&T);    while(T--)    {        scanf("%lf%lf%lf",&H,&h,&D);        double temp=sqrt((H-h)*D);        double temp2=(H-h)*D/H;        if(temp>=D)printf("%.3lf\n",h);        else if(temp<temp2)printf("%.3lf\n",h*D/H);        else        {            double ans=D+H-temp-(H-h)*D/temp;            printf("%.3lf\n",ans);        }    }    return 0;}


 

 

三分法:

#include<stdio.h>#include<iostream>#include<algorithm>#include<string.h>#include<math.h>using namespace std;const double eps=1e-9;double D,H,h;double calc(double x){    return D-x+H-(H-h)*D/x;}double solve(double l,double r){    double mid,midmid;    double d1,d2;    do    {        mid=(l+r)/2;        midmid=(mid+r)/2;        d1=calc(mid);        d2=calc(midmid);        if(d1>=d2) r=midmid;//这里要注意        else l=mid;    }    while(r-l>=eps);    return d1;}int main(){    int T;    scanf("%d",&T);    while(T--)    {        scanf("%lf%lf%lf",&H,&h,&D);        printf("%.3lf\n",solve((H-h)*D/H,D));    }    return 0;}








HDU   2438  Turn the corner(三分)

题意:已知汽车的长和宽,l和w,以及俩条路的宽为x和y,汽车所处道路宽为x ,问汽车能否顺利转弯?

第一种思路:

分析:汽车能否顺利转弯取决于在极限情况下,随着角度的变化,汽车离对面路的距离是否大于等于0

如图中

在上图中需要计算转弯过程中h 的最大值是否小于等于y

很明显,随着角度θ的增大,最大高度h先增长后减小,即为凸性函数,可以用三分法来求解


#include<iostream>#include<algorithm>#include<math.h>using namespace std;#define pi 3.141592653double x,y,l,w;double cal(double a){    double s=l*cos(a)+w*sin(a)-x;    double h=s*tan(a)+w*cos(a);    return h;}int main(){    while(scanf("%lf %lf %lf %lf",&x,&y,&l,&w)!=EOF)    {        double left=0.0,right=pi/2;        double lm,rm;        while(fabs(right-left)>1e-6)        {            lm=(left*2.0+right)/3.0;            rm=(left+right*2.0)/3.0;            if(cal(lm)>cal(rm))                right=rm;            else left=lm;        }        if(cal(left)<=y)            printf("yes\n");        else printf("no\n");    }    return 0;}


第二种思路:

可以根据边界,汽车已经转弯,设水平方向为x轴,垂直方向为y轴。

则汽车的内边界(靠近里面的边界)的直线方程式f(x)为:y=x*tan(a)+l*sin(a)+d/cos(a).其中a是汽车与x轴的夹角

当y=X时,求解出的-x即为汽车的内边界到y轴的距离h,若h小于Y即可转弯,若大于Y就不能转弯。
所以只需要利用方程式,求-x的最大值,即可判断能否通过。

简单来讲,就是判断图中P点的横坐标的绝对值是否小于Y?小于,则可通过;否则,不可通过。

由于f(x)是凸函数(随着x的增大y先增大后减小),所以,需要借助三分求解。(对角度a在[0,90]三分)

图示:




#include<iostream>#include<cstdio>#include<cstring>#include<cmath>using namespace std;#define MIN 1e-7#define PI acos(-1.0)double x,y,l,d;double Solve(double tmp){    return (-x+l*sin(tmp)+d/cos(tmp))/tan(tmp);}int main(){    while(~scanf("%lf%lf%lf%lf",&x,&y,&l,&d)){        double low=0,high=PI/2.0,mid,mmid;        if(x<d||y<d){ puts("no");continue; }        while( high-low>MIN ){            mid=(low+high)/2.0;            mmid=(mid+high)/2.0;            if(Solve(mid)>Solve(mmid))high=mmid+1e-9;            else low=mid-1e-9;        }        if(Solve(mid)<y){            puts("yes");        }else             puts("no");    }    return 0;}






HDU   4717  The Moving Points

大致题意:有n个点,给出每个点的初始坐标x,y和在x,y方向上的运动速度vx,vy。求时间t使得n个点中任意两点之间的距离最大值的最小,输出此时的时间t和任意两点间的最大距离的最小值。

分析:由于,各点的初始坐标和各方向运动速度已知,则可求出时间t后各点的坐标即(x+vx*t,y+vy*t)一般来讲,任意两动点之间的距离总是先小后大,而且题意也保证不会出现运动方向和速度均相同的两点,故任意两点之间的最大距离也是一个凹函数,可用三分求最小值。

要注意精度问题,要保证精度在1e-5以上。

代码:

#include <cstdio>#include <cstring>#include <cmath>#include <iostream>#include <algorithm>using namespace std;const double eps=1e-14;const int maxn=303;int n;struct node{    double x,y,vx,vy;}e[maxn*maxn];double cross(node a,node b,double t){    return (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 find(double t){    double ans=0;    int i,j;    for(i=0;i<n;i++)    for(j=i+1;j<n;j++)        ans=max(ans,cross(e[i],e[j],t));    return ans;}int main(){    int T,tt=0;    scanf("%d",&T);    while(T--)    {        int i,j,k;        scanf("%d",&n);        for(i=0;i<n;i++)        scanf("%lf%lf%lf%lf",&e[i].x,&e[i].y,&e[i].vx,&e[i].vy);        double l,r,m1,m2;        l=0,r=1e8;        for(i=0;i<100;i++)        {            m1=l+(r-l)/3;            m2=r-(r-l)/3;            if(find(m1)<find(m2)) r=m2;            else l=m1;        }        printf("Case #%d: %.2lf %.2lf\n",++tt,l,sqrt(find(l)));    }    return 0;}/*    每组点和速度,根据变量t,横纵坐标就是关于t的以为函数。    求两点距离的平方就是下凸函数。fi(x)=ai*t*t+bi*t+ci;    而求n*(n-1)/2个距离的最大值,s(x)=max{fi(x)},也是下凸函数。    因而可以用三分法求得结果。*/


HDU   2199 Can you solve this equation

水题一道~~~不过也是很经典的二分解线性方程的题目。

题意:给个方程 8*x^4 + 7*x^3 + 2*x^2 + 3*x + 6 ==Y,题目给出Y,在区间[0,100]内求解。

分析:求导可知该函数在[0,100]上是单增的,有单调性,故可用二分求解。

代码:

#include<cmath>#include<cstdio>#define eps 1e-8double fun(double x){    return 8*x*x*x*x + 7*x*x*x + 2*x*x + 3*x + 6;}int main(){    double y,a,b,m,d;    int T;    scanf("%d",&T);    while(T--)    {        scanf("%lf",&y);        if(y<fun(0) || y>fun(100))        {            printf("No solution!\n");            continue;        }        a=0.0;        b=100.0;        while(b-a>eps)        {            m=b+(a-b)/2;            if(fun(m)<y)  a=m;            else   b=m;        }        printf("%.4f\n",a);    }    return 0;}







POJ    3737  UmBasketella

题意:求一个面积为s的圆锥体的最大体积,以及对应的底面半径和高。

 

思路1:直接解出。(如果数学好的话~~~)

基础几何。注意面积 s = s侧面+s底面。对于PI,以后就取PI=acos(-1.0)

S=PI*r*l +PI*r*r
l=sqrt(r*r+h*h), 联立得, r*2= s*s/(PI*PI*h*h+2*PI*s)
V=(1/3)*PI*r*r*h,代入r*2,求导,令一阶导数为0,得出结果

h=sqrt(2*s/PI)
r=sqrt(s*s/(PI*PI*h*h+2*PI*s))
v=(1.0/3.0)*(s*s)*h/(PI*h*h+2*s)

代码:

<span style="color:#000000;">(204K, 0MS)#include<iostream>#include<cmath>using namespace std;const double PI = acos(-1.0);int main(){    double s, r,h, v;    while(scanf("%lf",&s) != EOF){       r = sqrt(s/PI)/2;       h = sqrt((s*s)/(PI*PI*r*r) - 2*s/PI);        v= PI*r*r*h/3;        printf("%.2lf\n%.2lf\n%.2lf\n",v, h, r);    }    return0;}</span>


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

代码:
#include <iostream>#include <cmath>#include <string.h>#include <cstdio>using namespace std;const double pi = acos(-1.0);const double eps = 1e-6;double cal(double x,double s){double R = (s - x * x * pi) / pi / x;double H = sqrt(R * R - x * x);return x * x * pi * H / 3.0;}int main(){double area;while(scanf("%lf",&area) != EOF){   double lp = 0.0,rp = sqrt(area / pi);   double r = 0.0;   while(lp + eps < rp){      double mid1 = lp + (rp - lp) / 3.0;  double mid2 = rp - (rp - lp) / 3.0;  double value1 = cal(mid1,area);  double value2 = cal(mid2,area);  //printf("value1 = %.2lf  value2 = %.2lf\n",value1,value2);  if(value1 >= value2){     //r = mid2;      rp = mid2;  }  else{  r = mid1;      lp = mid1;  }   }   double R = ( area - pi * r * r ) / ( pi * r );   double H = sqrt(R * R - r * r);   double V = pi * r * r * H / 3.0;   printf("%.2lf\n",V);   printf("%.2lf\n",H);   printf("%.2lf\n",r);}return 0;}


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






HDU   4355  Party All the Time

题意:n个人,都要去参加活动,每个人都有所在位置xi和Wi,每个人没走S km,就会产生S^3*Wi的“不舒适度”,求在何位置举办活动才能使所有人的“不舒适度”之和最小,并求最小值。

分析:显然,举办活动的位置在最左侧的那个人和最右侧那个人之间,而且从最左侧到最右侧,所有人的Si^3*Wi之和先减小后增大,故可对位置从最左侧的那个人位置到最右侧那个人位置三分。
还是要注意精度,精度很容易出问题。


代码:
<span style="font-family:KaiTi_GB2312;">#include<iostream>#include<cstdlib>#include<stdio.h>#include<math.h>#include<algorithm>using namespace std;const double eps = 1e-12;struct Node{    double x;    double w;}node[50010];int n;double calc(double xx){    double res=0;    for(int i=1;i<=n;i++)    {        double d=xx-node[i].x;        if(d<0) d*=-1;        res+=d*d*d*node[i].w;    }    return res;}int main(){    int t;    scanf("%d",&t);    int count=1;    while(t--)    {        scanf("%d",&n);        for(int i=1;i<=n;i++)        scanf("%lf%lf",&node[i].x,&node[i].w);        double l=node[1].x;        double r=node[n].x;        double mid,midmid;        double mid_area;        double midmid_area;        while(l+eps<r)        {            mid=(l+r)/2;            midmid=(mid+r)/2;            mid_area=calc(mid);            midmid_area=calc(midmid);            if(mid_area<midmid_area)            r=midmid;            else            l=mid;        }        double g=calc(l);        double h=calc(r);        if(g<h)        printf("Case #%d: %.0lf\n",count++,g);        else        printf("Case #%d: %.0lf\n",count++,h);    }    return 0;}</span>




HDU   3400  Line belt


题意:有AB,CD两线段,在速度为p,在CD上的速度为q,其余地方速度为r,求A点到D点的最短时间。

分析:三分套三分。
具体看时,时间最小的情况肯定是先在AB上走一小段,然后直接到在CD上走一小段,最后到达D。
这样,先对离开AB上的位置三分枚举,在每一次枚举时,把离开AB的位置当成是定点,在这种情况下,再对到达CD上的点三分枚举,这样就可以求出此次三分AB的最小时间,然后完成对AB的三分后,就会得到从A到D的最短时间。

代码:
#include<stdio.h>#include<iostream>#include<math.h>#include<iostream>#include<algorithm>#include<string.h>using namespace std;const double eps=1e-5;struct point{    double x,y;};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 p,q,r;double find2(point a,point c,point d){    point left,right;    point mid,midmid;    double t1,t2;    left=c;right=d;    do    {        mid.x=(left.x+right.x)/2;        mid.y=(left.y+right.y)/2;        midmid.x=(mid.x+right.x)/2;        midmid.y=(mid.y+right.y)/2;        t1=dis(a,mid)/r+dis(mid,d)/q;        t2=dis(a,midmid)/r+dis(midmid,d)/q;        if(t1>t2)left=mid;        else right=midmid;    }    while(dis(left,right)>=eps);    return t1;}double find(point a,point b,point c,point d){    point left,right;    point mid,midmid;    double t1,t2;    left=a;    right=b;    do    {        mid.x=(left.x+right.x)/2;        mid.y=(left.y+right.y)/2;        midmid.x=(mid.x+right.x)/2;        midmid.y=(mid.y+right.y)/2;        t1=dis(a,mid)/p+find2(mid,c,d);        t2=dis(a,midmid)/p+find2(midmid,c,d);        if(t1>t2)left=mid;        else right=midmid;    }while(dis(right,left)>=eps);    return t1;}int main(){    int T;    point a,b,c,d;    scanf("%d",&T);    while(T--)    {        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);        printf("%.2lf\n",find(a,b,c,d));    }    return 0;}







HDU   4004  The Frog‘s Games

题意:有一只青蛙,要过一条河,河宽为L,河中间有n块石头,给出每块石头距青蛙一侧河岸的距离,青蛙只能踩在石头上,不能落入水中,青蛙最多能跳m次,问青蛙的弹跳能力最少为多远。

分析:
只需在所给的各石头的位置左边加一块位置为0的石头,在右边加一个位置为L的石头,这样就可以算出n+2块石头相邻两块之间的间距,然后就把这个问题转化成了最大值最小化的问题。同样,记在相邻两石块间隔的最大值为max,那就只需在[max,L]之间二分枚举青蛙的弹跳能力。

代码:
#include <stdio.h>#include <string.h>#include <iostream>#include <algorithm>#include <vector>#include <queue>#include <set>#include <map>#include <string>#include <math.h>#include <stdlib.h>#include <time.h>#define INF 0x7fffffff#define  LL  long longusing namespace std;int L,n,m;int a[500001],b[500001];bool ok(int x){    int s=0,t=1;    for(int i=0;i<=n;i++)    {        s+=b[i];        if(s>x)        {            t++;            s=b[i];        }    }    if(t>m)  return  false;    return  true;}int main(){    while(scanf("%d%d%d",&L,&n,&m)!=EOF)    {        a[0]=0;        a[n+1]=L;        for(int i=1;i<=n;i++)            scanf("%d",&a[i]);        sort(a,a+n+2);        int max=0;        for(int i=0;i<n+1;i++)        {            b[i]=a[i+1]-a[i];            if(max<b[i])  max=b[i];        }        if(m>n)  { printf("%d\n",max); continue; }        int l=max,r=L;        while(l<r)        {            int mid=(l+r)/2;            if(!ok(mid)) l=mid+1;            else  r=mid;        }        printf("%d\n",r);    }    return 0;}




后记:终于写完了,好了,要A题了~~~

0 0
原创粉丝点击