二解 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;
}

以前只想到用三分寫此題,今年小白們數學好,硬是拿數學來分析得到了這個數學方法,教學相長也!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章