動態規划3--Help Jimmy
一、心得
二、題目
三、分析
Jimmy跳到一塊板上后,可以有兩種選擇,向左走,或向右走。
走到左端和走到右端所需的時間,是很容易算的。
如果我們能知道,以左端為起點到達地面的最短時間,和以右端為起點到達地面的最短時間,那么向左走還是向右走,就很容選擇了。
因此,整個問題就被分解成兩個子問題,即Jimmy所在位置下方第一塊板左端為起點到地面的最短時間,和右端為起點到地面的最短時間。
這兩個子問題在形式上和原問題是完全一致的。將板子從上到下從1開始進行無重復的編號(越高的板子編號越小,高度相同的幾塊板子,哪塊編號在前無所謂),那么,和上面兩個子問題相關的變量就只有板子的編號。
將板子由高到低按從0到n編號,起始點的為0
不妨認為Jimmy開始的位置是一個編號為0,長度為0的板子
設LeftMinTime(k)表示從k號板子左端到地面的最短時間
RightMinTime(k)表示從k號板子右端到地面的最短時間
1 if ( 板子k左端正下方沒有別的板子) { 2 if( 板子k的高度 h(k) 大於Max) 3 LeftMinTime(k) = ∞; 4 else 5 LeftMinTime(k) = h(k); 6 } 7 else if( 板子k左端正下方的板子編號是m ) 8 LeftMinTime(k) = h(k)-h(m) + 9 Min( LeftMinTime(m) + Lx(k)-Lx(m), 10 RightMinTime(m) + Rx(m)-Lx(k)); 11 }
上面,h(i)就代表i號板子的高度,Lx(i)就代表i號板子左端點的橫坐標,Rx(i)就代表i號板子右端點的橫坐標。那么 h(k)-h(m) 當然就是從k號板子跳到m號板子所需要的時間,Lx(k)-Lx(m) 就是從m號板子的落腳點走到m號板子左端點的時間,Rx(m)-Lx(k)就是從m號板子的落腳點走到右端點所需的時間。
求RightMinTime(k)的過程類似。
不妨認為Jimmy開始的位置是一個編號為0,長度為0的板子,那么整個問題就是要求LeftMinTime(0)。
輸入數據中,板子並沒有按高度排序,所以程序中一定要首先將板子排序。
時間復雜度: 一共 n個板子,每個左右兩端的最小時間各算一次 O(n) 找出板子一段到地面之間有那塊板子,需要遍歷板子 O(n) 總的時間復雜度O(n2)
四、代碼及結果
1、記憶化遞歸
1 /* 2 POJ1661 Help Himmy 3 這樣效率太低了,一早上沒看幾個題 4 代碼要是不是特別好看懂,先把偽代碼寫出來就比較好懂了 5 6 分析: 7 將板子由高到低按從0到n編號,起始點的為0 8 不妨認為Jimmy開始的位置是一個編號為0,長度為0的板子 9 設LeftMinTime(k)表示從k號板子左端到地面的最短時間 10 RightMinTime(k)表示從k號板子右端到地面的最短時間 11 if ( 板子k左端正下方沒有別的板子) { 12 if( 板子k的高度 h(k) 大於Max) 13 LeftMinTime(k) = ∞; 14 else 15 LeftMinTime(k) = h(k); 16 } 17 else if( 板子k左端正下方的板子編號是m ) 18 LeftMinTime(k) = h(k)-h(m) + 19 Min( LeftMinTime(m) + Lx(k)-Lx(m), 20 RightMinTime(m) + Rx(m)-Lx(k)); 21 } 22 */ 23 #include <iostream> 24 #include <cstdio> 25 #include <algorithm> 26 #include <cstring> 27 using namespace std; 28 #define MAX_N 1000 29 #define INFINITE 1000000 30 int t,n,x,y,maxHeight; 31 struct Platform{//定義平台結構體 32 int Lx, Rx, h;//Lx表示做邊界橫坐標,Rx表示右邊界橫坐標,h表示高度 33 bool operator < (const Platform & p2) const {//符號重載,sort的時候能按h從高到低排 34 return h > p2.h; 35 } 36 }; 37 Platform platForms[MAX_N + 10];//平台 38 int leftMinTime[MAX_N + 10];//走左邊的最小時間 39 int rightMinTime[MAX_N + 10];//走右邊的最小時間 40 int L[MAX_N + 10];// 41 //l表示現在這塊板的編號,越在上面的編號越小,bleft表示是否向左邊走 42 //因為這題分為向左和向右兩種情況 43 int MinTime( int l, bool bLeft )//l表示現在這塊板的編號,越在上面的編號越小,bleft表示是否向左邊走 44 { 45 //初始化x和y坐標,如果是去左邊,就走到左邊邊上,如果去右邊,就走到右邊邊上 46 int y = platForms[l].h; 47 int x; 48 //如果是去左邊,就走到左邊邊上,如果去右邊,就走到右邊邊上 49 if(bLeft) 50 x = platForms[l].Lx; 51 else 52 x = platForms[l].Rx; 53 int i; 54 for( i = l + 1;i <= n;i ++ ) {//找到現在這塊板下面的那塊板 55 if( platForms[i].Lx <= x && platForms[i].Rx >= x)//判斷從當前條能跳到的小一塊板子上 56 break; 57 } 58 if( i <= n ) {// 板子k左端正下方有別的板 59 if( y - platForms[i].h > maxHeight )// 跳到這塊平台的高度如果大於Max 60 return INFINITE;//返回無限大 61 } 62 else {// 板子k左端正下方沒有別的板 63 if( y > maxHeight )//板子k的高度 h(k) 大於Max 64 return INFINITE;//返回無限大 65 else 66 return y;//如果可以直接跳下,就輸出y 67 } 68 int nLeftTime = y - platForms[i].h + x - platForms[i].Lx;//現在平台與下一塊平台的高度差以及下一塊平台左邊界的距離 69 int nRightTime = y - platForms[i].h + platForms[i].Rx - x;//現在平台與下一塊平台的高度差以及下一塊平台右邊界的距離 70 if( leftMinTime[i] == -1 ) //等於-1表示我初始化過 ,如果還可以向左我們就向左 71 leftMinTime[i] = MinTime(i,true);//向左進入子問題 72 if( L[i] == -1 )//等於-1表示我初始化過 ,如果還可以向右我們就向右 73 L[i] = MinTime(i,false);//像右進入子問題 74 nLeftTime += leftMinTime[i];//左邊固定花費的時間加上下一場左邊這樣的時間 75 nRightTime += L[i];//右邊固定花費的時間加上下一場右邊這樣的時間 76 //返回左邊和右邊走中值小的那一個 77 if( nLeftTime < nRightTime ) 78 return nLeftTime; 79 return nRightTime; 80 } 81 82 int main() { 83 freopen("in.txt","r",stdin); 84 scanf("%d",&t);//讀入t組數據 85 for( int i = 0;i < t; i ++ ) {//對每組數據進行操作 86 memset(leftMinTime,-1,sizeof(leftMinTime));//初始化leftMinTime 87 memset(L,-1,sizeof(rightMinTime));//初始化L 88 scanf("%d%d%d%d",&n, &x, &y, &maxHeight);//讀入數據 89 platForms[0].Lx = x; platForms[0].Rx = x; 90 platForms[0].h = y;//起始點初始化 91 for( int j = 1; j <= n; j ++ )//讀入平台信息 92 scanf("%d%d%d",&platForms[j].Lx,& platForms[j].Rx, & platForms[j].h); 93 sort(platForms,platForms+n+1);//對平台由高到低排序 94 printf("%d\n", MinTime(0,true));//MinTime()方法求下平台的最小時間 95 } 96 return 0; 97 }