題目傳送門: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;
}
以前只想到用三分寫此題,今年小白們數學好,硬是拿數學來分析得到了這個數學方法,教學相長也!