100層樓扔兩個雞蛋問題


解釋:兩個雞蛋一樣,只有在達到某個樓層高度時,才會摔碎。可以假設這個摔碎臨界樓層是N。

1、最笨的方法——只用一個雞蛋遍歷——N次嘗試

  • 一個雞蛋遍歷那就是從一樓頂開始,逐層嘗試,如果摔不碎那就繼續往上層嘗試,直到N層摔碎了。這樣就嘗試了N次,而且浪費了一個雞蛋的使用。

2、二分查找——兩個雞蛋,雞蛋A用來二分嘗試,雞蛋B用來在A摔碎后做局部遍歷嘗試

  • 雞蛋A用來做二分嘗試,即第一次從50層扔下。
  • 最悲觀情況,直接摔碎,說明N在1-50之間,那么雞蛋B也只能從1開始遍歷,回到了第一種情況(最多嘗試次數也是N)。
  • 樂觀情況,雞蛋A沒摔碎,接下來就可以嘗試從75層扔下,碎了那就是N在51-74之間了。嘗試次數為1+1+(74-51)=25次。
  • 更樂觀情況,雞蛋A在75層也沒碎,接下來可以在87層扔下;A碎了則N在76-86之間,故是需要1+1+1+(86-76)=13次。
  • A沒碎,接下來在93層扔下;A碎了則N在88-92之間,故需要扔1+1+1+1+(92-88)=8次。
  • A沒碎,接下來在96層扔下;A碎了則N在94-95之間,故需要扔1+1+1+1+1+(95-94)=6次。
  • A沒碎,接下來在98層扔下;A碎了則N在97,故需要扔1+1+1+1+1+1=6次。
  • A沒碎,則A在99-100之間,如需要扔6+1=7次。

可見,用二分法結果很不穩定,特別是N小於50時最糟糕(甚至會比第一種直接遍歷的還要多一次)。N越大越好找,需要嘗試的次數越少。
如果這個題目換成雞蛋個數不限制,那就是用二分法最快了。

3、平均分割樓層法——假設總共扔X次,其中雞蛋A扔了X1次,雞蛋B扔了X2次

  • X=X1+X2
  • 雞蛋A用來做樓層平均分割,大步嘗試;雞蛋B作為每一小部分的遍歷小步嘗試。
  • 假設將100層平均分為10部分,即雞蛋A分別在第10、20、30、40、50、60、70、80、90、100層扔;則雞蛋B在A摔碎后在細分的那個樓層小步遍歷尋找即可。如此的平均嘗試次數又要比二分查找更好。
  • 但問題是如何找到最優的平均分割n段,X1=n,X2=100/n。
    X=n+100/n,可見n平方=100即n等於10時,X=20。
  • 若能在后面每一段更准確地分析出應該分的樓層數(如圖2),而不是平均10層一段(如圖1),會有更優的效果。下一個方法就是這樣。


     
     

4、假設法——假設最多允許嘗試X次,問能嘗試到的最高的樓層。

  • 第1次從X樓扔下來。因為即使摔壞了,也可以用另一個雞蛋遍歷X-1次找到該樓層。
  • 第2次(還剩X-1次嘗試次數)可以從X+(X-1)層扔下來。因為即使摔碎了,也可以用另一個雞蛋遍歷X-1-1次找到該樓層。
  • 同理,第3次,可以從X+(X-1)+(X-2)層扔下來。
  • 第X次。可以從第X+(X-1)+(X-2)+...+(X-(X-2))+1層扔下來,這就是最高可能嘗試到的樓層X*(X+1)/2,下面所有的樓層都可以在X次嘗試中到達。

當最高樓層為100時,可列出不等式:最高可能嘗試到的樓層X*(X+1)/2 > 100,解出X=14次。這就是最穩定的最快尋找到該樓層的扔雞蛋次數。也就是說第一次扔雞蛋要從14樓開始扔。14+13+12+11+...+2+1 = 105層,也就是14次嘗試一定可以在1-105層中找到那個第N層。推出了公式X*(X+1)/2后,要想編程求任意總樓層條件下,就都很方便了。

5、動態規划法——找最優解常用方法

在我們編程解決問題的過程中,如果遇到最優問題的時候,往往可以先嘗試一下動態規划的方法。而動態規划的方法,首要的我們要找到構成這個最優問題的最優子問題。所以,下面的分析,我們首先嘗試動態規划的方法,如何解決這個問題,這也是典型的程序員的思路;其次,在眾多的問題當中,有不少可以直接歸結為數學方程式,如果我們能夠寫出數學方程式,那么,答案將是更加的簡潔、美妙(比如上一種方法推導出來的公式)。

  • 基於動態規划的方法 前面提到,若要采用動態規划的方法,最重要的是要找到子問題。做如下的分析,假設F{n}表示從第n層樓扔下雞蛋,找到不摔碎雞蛋樓層的最少嘗試次數。第一個雞蛋可能從第i層扔下,有兩個情況:

  • 碎了,第二個雞蛋,需要從第一層開始試驗,最多要嘗試i-1次。

  • 沒碎,兩個雞蛋,還有n-i層。這個就是子問題了f[n-i] 。

所以,當第一個雞蛋,由第i個位置落下的時候,要嘗試的次數為f[i]= 1 + max(i - 1, f[n-i])用max是確保一定可以在這么多次內找到。那么對於每一個i對f(i)進行比較,非最小的f(i),就是F{n}的值。狀態轉移方程如下: F{n} = min f[i] = min(1 + max(i - 1, f[n-i]) ) 其中: i的范圍為(1, n), f[1] = 1 完畢。

推廣動態規划的方法,可以推廣為n層樓,m個雞蛋。如下分析: 假設f{n,m}表示n層樓、m個雞蛋時找到最高樓層的最少嘗試次數。當第一個雞蛋從第i層扔下,如果碎了,還剩m-1個雞蛋,為確定下面樓層中的安全樓層,還需要f{i-1,m-1}次,找到子問題;不碎的話,上面還有n-i層,還需要f[n-i,m]次,又一個子問題。 狀態轉移方程如下: f{n, m} = min(1 + max(f{i - 1, m - 1}, f{n - i, m}) ) 其中: i為(1, n), f{i, 1} = 1

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <algorithm>
 4 
 5 using namespace std;
 6 const int N = 55;
 7 const int M = 1010;
 8 int dp[N][M];//剩余i個雞蛋在j層樓進行的最少次數
 9 int n, m;
10 
11 void init()
12 {
13     memset(dp, 0, sizeof(dp));
14     for(int i = 1; i < N; ++i)
15     {
1617         dp[i][1] = 1;
18     }
19     for(int i = 1; i < M; ++i)
20         dp[1][i] = i;
21     for(int i = 2; i < N; ++i)
22         for(int j = 2; j < M; ++j)
23             for(int k = 1; k < j; ++k)//最壞情況的最少次數,碎和沒碎兩種情況取最大值
24                 dp[i][j] = min(dp[i][j], max(dp[i][j-k]+1, dp[i-1][k-1]+1));
25 }
26 
27 int main()
28 {
29     int t;
30     scanf("%d", &t);
31     init();
32     while(t--)
33     {
34         int cas;
35         scanf("%d %d %d", &cas, &n, &m);
36         printf("%d %d\n", cas, dp[n][m]);
37     }
38     return 0;
39 }

 





免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM