二解 ZOJ3203 Light Bulb(数学和三分)

题目传送门:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3203
题目大意是:
  相比 wildleopard 的家,他的弟弟 mildleopard 比较穷。他的房子是狭窄的而且在他的房间里面仅有一个灯泡。每天晚上,他徘徊在自己狭小的房子里,思考如何赚更多的钱。有一天,他发现他的影子的长度随着他在灯泡和墙壁之间走到时发生着变化。一个突然的想法出现在脑海里,他想知道他的影子的最大长度。
  在这里插入图片描述【输入】
  输入文件的第一行包含一个整数 T ,表示测试数据的组数。
  对于每组测试数据,仅一行,包含三个实数H,h和D,H表示灯泡的高度,h表示 mildleopard 的身高,D 表示灯泡和墙的水平距离。
【输出】
  输出文件共 T 行,每组数据占一行表示影子的最大长度,保留三位小数。
【样例输入】
3
2 1 0.5
2 0.5 3
4 3 4
【样例输出】
1.000
0.750
4.000
【提示】
T≤100,10−2≤H,h,D≤103,10^(−2)≤H−h。
一、数学分析:我们发现,人的影子有三种状态:
  1.影子全在地上。
  2.影子一部分在地上,一部分在墙上。
  3.影子全在墙上。
就情况1来说:当人从灯下往墙的方向走时,影子是逐渐在变长的直到如下图所示。也就是灯泡,人头顶和墙角底端是一条直线时,这时是影子在地上的最长,按照数学的相似三角形计算,也就是人距离灯泡D-Dh/H时为此种状态的最大。
在这里插入图片描述情况2来说:在当x(x为与灯泡的距离)在(l1 , D)区间内时,影子是一部分在地上,一部分在墙上的.,通过计算设人与灯泡的距离为x,墙上的影子长度为y,
  则(h-y)/(H-y) = (D - x)/D,
  转化成含有x的函数表达y后,y = H - D
(H-h)/x.
  因为L= y + D - x
  所以L = D + H - x - D*(H-h)/x.
  其中后面的含有x的部分是对勾函数(f(x) = x + D*(H-h)/x).
  因此L = D+H - f(x),当f(x)有最小值时,L有最大值。根据对勾函数的最值公式,可以知道最大值L=D+H- 2sqrt(D(H-h)).
在这里插入图片描述在这里插入图片描述情况3:影子全在墙上时,影长为h.
情况分析了后,那么这道看似特别难的题就这样变成了数学推算和分支语法的题了,具体代码:

#include<bits/stdc++.h>
using namespace std;
 
int main(){
    int z;
    double H,h,D;
    cin >> z;
    for(int i=0;i<=z-1;i++){
        cin >> H>> h >> D;
        double x1 = sqrt(D*(H-h));
        double xx0 = D*h/H;
        if( x1 >=D || x1 <=D - xx0){  //情况1与3
            if(h>=xx0)   printf("%.3lf\n",h);
            else         printf("%.3lf\n",xx0);
            
        }
        else if(x1 > D - xx0 &&x1 < D){ //情况2
	            printf("%.3lf\n",D+H-2*x1);
	        }
        }
    return 0;
}

二、三分答案
介于上面的分析,我们知道此问题的解一定在区间[D-Dh/H,D]之间,并且已经分析出L = D + H - x - D(H-h)/x.是一个对勾函数的一部分,它是一个有单个峰值的图像,可以采用三分的方式,逐渐逼近峰值求得解。因此使用三分模板来解,如下所示:

#include <bits/stdc++.h>  
using namespace std;
double H,h,D;
double cal(double x){
    return D-x+H-D*(H-h)/x;
}
int main(){
    double left , right;
    int t;
    cin >> t;
    while(t)
    {
          cin >> H >> h >> D;
          left = D - D*h/H;
          right = D;
          while(right - left > 1e-10){
            double lmid = left + (right - left)/3;
            double rmid = right - (right - left)/3;
            if(cal(lmid) < cal(rmid)) left = lmid;
            else right = rmid; 
          }
          printf("%.3lf\n",cal(left));
          t--;
    }    
    return 0;
}

以前只想到用三分写此题,今年小白们数学好,硬是拿数学来分析得到了这个数学方法,教学相长也!

发布了88 篇原创文章 · 获赞 22 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/xuechen_gemgirl/article/details/89498022