昨天早上看了題經典老題,拋玻璃球,也有的版本是拋雞蛋,可惜昨天早上愣是沒做出來,下午忙別的事去了,到了晚上看了ChinaUnix上的一篇
討論帖才知道如何解,事實上我一開始對題目的理解就錯了,于是根本沒有想到用DP。今天總算有時間整理一下思路,并把代碼實現出來了。
題目是這樣的:一個100層的大廈,你手中有兩個相同的玻璃球。從這個大廈的某一層扔下圍棋
子就會碎,用你手中的這兩個玻璃圍棋子,找出一個最優的策略,來得知那個臨界層面。
這里的最優策略指的是在這種策略下無論哪個臨界層面在第幾層,測試的次數最少。我一開始就是把題意理解錯了,給了一個非最優解,后來看了CU那的討論后才明白了是用動態規劃來做,并可以把題目擴展為n層大廈用k個玻璃球來測試。
設F(n,k)為用k個玻璃球來測試n層大廈的臨界層的最少次數,狀態轉移方程如下:
F(n,k)=min{max{F(r,k-1), F(n-r,k)}+1, 1<=r<=n}
邊界條件:F(n,1)=n-1, F(1,k)=F(0,k)=0
狀態轉移方程可以這樣來考慮,假設在n層樓中的第r層拋一次(對應方程中的"+1"),會有兩種情況發生:
(1)玻璃球碎,說明在第1到第r層樓中必有一層為臨界層,問題轉化為一個子問題:求F(r,k-1)
(2)玻璃球不碎,說明臨界層在第r+1層到第n層這n-r層樓中,問題轉化為子問題:求F(n-r,k)
因為考慮的是最壞情況下拋球策略的所需測試次數的最小值,所以取這兩種情況中的較大值,并遍歷每一個可能的r,取其最小值即得到F(n,k)。
實現代碼如下:
1 #include <iostream>
2 #include <fstream>
3 #include <sstream>
4 #include <string>
5 #include <cmath>
6 #include <iomanip>
7 #include <vector>
8 #include <deque>
9 #include <list>
10 #include <queue>
11 #include <stack>
12 #include <map>
13 #include <algorithm>
14 #include <limits>
15 #include <utility>
16 #include <ctime>
17 #include <bitset>
18 using namespace std;
19
20 #define MAX_FLOOR 512
21 #define MAX_BALL 100
22
23 int dp(int n, int k)
24 {
25 if(k<1 || n<1) return -1; //錯誤輸入
26
27 if(k==1) return n-1; //去掉一些trivial case
28 if(n==1) return 0;
29
30 int M[MAX_BALL][MAX_FLOOR];
31 int i,j,r;
32 int temp, min;
33
34 for(i=0;i<=k;i++) M[i][0]=M[i][1]=0; //F(1,k)=F(0,k)=0
35 for(j=2;j<=n;j++) M[1][j]=j-1; //F(n,1)=n-1
36
37 /*
38 狀態轉移方程:
39 F(n,k)=min{max{F(r,k-1)+1, F(n-r,k)+1}, 1<=r<=n}
40 */
41 for(i=2;i<=k;i++)
42 for(j=2;j<=n;j++)
43 {
44 min = numeric_limits<int>::max();
45 for(r=1;r<=j;r++)
46 {
47 temp = max(M[i-1][r], M[i][j-r])+1;
48 if(temp<min)
49 min = temp;
50 }
51 M[i][j] = min;
52 }
53
54 return M[k][n];//F(n,k)
55 }
56
57 int main()
58 {
59 int n,k;
60
61 cin>>n>>k;
62 cout<<dp(n, k)<<endl;
63
64 return 0;
65 }
input: 100 2 output: 14
input: 300 3 output: 13