動態規劃專題之石子合併
阿新 • • 發佈:2018-12-15
動態規劃專題講義 專題九:合併石子問題 /* Name: 動態規劃專題之石子合併 Author: 巧若拙 Description: 在一個操場上擺放著一排N堆石子。現要將石子有次序地合併成一堆。規定每次只能選相鄰的2堆石子合併成新的一堆,並將新的一堆石子數記為該次合併的得分。 試設計一個演算法,計算出將N堆石子合併成一堆的最小得分。 輸入描述 Input Description 第一行是一個數N。1≤N≤100 以下N行每行一個數A,表示石子數目。1≤A≤200 輸出描述 Output Description 共一個數,即N堆石子合併成一堆的最小得分。 樣例輸入 Sample Input 7 13 7 8 16 21 4 18 樣例輸出 Sample Output 239 */ #include<iostream> using namespace std; const int INT_MAX = 2147483647; const int MAX = 1000; int A[MAX+1];//記錄每堆石子的數量 int Sum[MAX+1];//記錄前n堆石子的數量 int B[MAX+1][MAX+1];//記錄第i堆石子至第j堆石子的最優解 int S[MAX+1][MAX+1];//記錄從哪裡斷開的才可得到最優解 bool flag[MAX+1]; //記錄A[i]是否已經被輸出過 int StonesCombined(int i, int j);//自頂向下,使用備忘錄陣列的動態規劃演算法 int StonesCombined_2(int n);//自底向上的動態規劃演算法:遞增括號中石子堆數量 int StonesCombined_3(int n);//自底向上的動態規劃演算法:逆序掃描 int StonesCombined_4(int n);//自底向上的動態規劃演算法:順序掃描 void Traceback(int i, int j);//根據s[][]記錄的各個子段的最優解,將其輸出 int main() { int n = 0; cin >> n; for (int i=1; i<=n; i++) { cin >> A[i]; Sum[i] = Sum[i-1] + A[i]; } // cout << StonesCombined(1, n) << endl;//自頂向下,使用備忘錄陣列的動態規劃演算法 cout << StonesCombined_2(n) << endl;//自底向上的動態規劃演算法 Traceback(1, n); cout << endl; return 0; } 演算法1:自頂向下,使用備忘錄陣列的動態規劃演算法,需要用到全域性變數S[MAX+1][], 另有B[MAX+1][]初始化為0。 int StonesCombined(int i, int j) { if (B[i][j] != 0) return //語句1 if (i == j) return //語句2 int t, m = INT_MAX; for (int k=i; k<j; k++) { t = StonesCombined(i, k) + StonesCombined(k+1, j) + Sum[j] - Sum[i-1]; if (t < m) { m = t; S[i][j] = k; //記錄從哪裡斷開的才可得到最優解 } } return B[i][j] = m; } 問題1:將語句1和語句2補充完整。 參考答案: 問題1:語句1:return B[i][j]; 語句2:return 0; 演算法2:自底向上的動態規劃演算法:遞增括號中石子堆數量,需要用到全域性變數S[MAX+1][], 另有B[MAX+1][]初始化為0。 int StonesCombined_2(int n) { for (int len=2; len<=n; len++) //語句1 { for (int i=1; i<=n-len+1; i++) //左邊界 { int j = //語句2 int t, m = INT_MAX; for (int k=i; k<j; k++) { t = B[i][k] + B[k+1][j] + Sum[j] - Sum[i-1]; if (t < m) { m = t; S[i][j] = k; } } B[i][j] = m; } } return B[1][n]; } 問題1:語句1能否改為:for (int len=n; len>=2; len--) ?為什麼? 問題2:將語句2補充完整。 參考答案: 問題1:不能。len代表當前被合併在一起的石子堆的數量,演算法的思想是先計算所有相鄰的2堆石子合併在一起的解,再計算所有所有相鄰的3堆石子合併在一起的解,逐步擴大子問題的規模。在計算較大規模的問題時,可以呼叫較小規模子問題的解。所以len必須從小到大。 問題2:語句2:int j = i + len - 1; //右邊界 演算法3:自底向上的動態規劃演算法:逆序掃描,需要用到全域性變數S[MAX+1][], 另有B[MAX+1][]初始化為0。 int StonesCombined_3(int n) { for (int i=n-1; i>0; i--) { for (int j=i+1; j<=n; j++)// 語句1 { int t, m = INT_MAX; for (int k=i; k<j; k++) // 語句2 { t = B[i][k] + B[k+1][j] + Sum[j] - Sum[i-1]; if (t < m) { m = t; S[i][j] = k; } } B[i][j] = m; } } return B[1][n]; } 問題1:語句1能否改為:for (int j=n; j>i; j--)?為什麼? 問題2:語句2能否改為:for (int k=j-1; k>=i; k--)?為什麼? 參考答案: 問題1:不能。i和j分別代表當前被合併石子堆的左,右邊界,演算法3的基本思想是在確定左邊界的情況下,逐步擴大子問題的右邊界。在計算較大規模的問題時,可以呼叫較小規模子問題的解。所以j必須遞增。 問題2:可以。k代表分隔石子堆的位置(即把[i,k]和[k+1,j]兩堆石子合併成[i,j]一堆石子),只要滿足i<=k<j,k遞增或遞減均可。 演算法4:自底向上的動態規劃演算法:順序掃描,需要用到全域性變數S[MAX+1][], 另有B[MAX+1][]初始化為0。 int StonesCombined_4(int n) { for (int j=2; j<=n; j++) { for (int i=j-1; i>0; i--)// 語句1 { int t, m = INT_MAX; for (int k=i; k<j; k++) { t = B[i][k] + B[k+1][j] + Sum[j] - Sum[i-1]; if (t < m) { m = t; S[i][j] = k; } } B[i][j] = m; } } return B[1][n]; } 問題1:語句1能否改為:for (int i=1; i<j; i++)?為什麼? 參考答案: 問題1:不能。i和j分別代表當前被合併石子堆的左,右邊界,演算法4和演算法3的思路剛好相反,它是在確定右邊界的情況下,逐步擴大子問題的左邊界,所以i必須遞減。 拓展練習:原題只要求輸出最優解,並未要求輸出具體的合併方法。 但是我們在上述演算法中引入了一個二維陣列S[][]記錄k的值,即從哪裡分開石子堆才能得到最優解。現在請你根據S[][]記錄的資訊,設計一個遞迴函式void Traceback(int i,int j);// i和j分別代表當前被合併石子堆的左,右邊界。 請用括號把合併在一起的石子堆括起來,輸出最終合併情形。 例如:對於題目給出的樣例:13 7 8 16 21 4 18,則輸出:(((13 (7 8))16)(21 (4 18))) 參考答案: void Traceback(int i,int j)//根據s[][]記錄的各個子段的最優解,將其輸出 { if (i == j) return ; cout << "("; Traceback(i, S[i][j]); if (flag[i] == 0) { cout << A[i] << " "; flag[i] = 1; } Traceback(S[i][j]+1, j); if (flag[j] == 0) { cout << A[j]; flag[j] = 1; } cout << ")"; } 課後練習: 練習1: 3546_矩陣鏈乘法 描述:給定有n個要相乘的矩陣構成的序列(鏈)<A1,A2,A3,.......,An>,要計算乘積A1A2.....An。一組矩陣是加全部括號的。 矩陣鏈加括號對運算的效能有很大影響。 僅當兩個矩陣A和B相容(即A的列數等於B的行數),才可以進行相乘運算。如果A是一個p×q矩陣,B是q×r矩陣,結果C是p×r的矩陣。 計算C的時間由乘法運算次數決定的,次數為p×q×r。 矩陣鏈乘法問題可表述為:給定n個矩陣構成的一個鏈<A1,A2,A3.......,An>,其中i=1,2,3,4.....,n,矩陣Ai的維數為Pi-1 ×Pi, 對乘積A1A2A3.....An,以一種最小標量乘法次數的方式進行加全部括號。 輸入描述 Input Description 如果有n個數組,則第一行輸入n+1個整數值 輸出描述 Output Description 所有的陣列以一種最小標量乘法次數的方式進行加全部括號。 樣例輸入 Sample Input 1 2 3 4 樣例輸出 Sample Output ((A1A2)A3)