题目传送门: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;
}
以前只想到用三分写此题,今年小白们数学好,硬是拿数学来分析得到了这个数学方法,教学相长也!