本周集訓專題為DP系列,一個經典的系列便是石子歸並問題。
(1)有N堆石子,現要將石子有序的合並成一堆,規定如下:每次只能移動相鄰的2堆石子合並,合並花費為新合成的一堆石子的數量。求將這N堆石子合並成一堆的總花費最小(或最大)。
這是石子歸並的簡化版本,石子處於一排。由於發現只能是相鄰的2堆石子進行歸並。我們會發現,貪心算法在此處便失去作用,局部最優解並不能帶來整體最優解。
因此,不難讓我們想到,此題應該采取DP(dynamic Programing)來求其最優解。
動態規划常常采取從部分整體最優解的拆分來得到最優解法的遞歸式,我們可以想到,此處是由2堆石子合並,所以最終最優解肯定是由兩個局部最優解的加上整體的和求得。
因此,我們可以推斷出動態轉移方程:
dp[i][j]在此處表示從第i堆加到第j堆的最優解,而當i == j是,並不存在相加,所以結果為0.
(2)將上述問題從一排改為環形,便是完整版本的石子合並問題了。
不難發現,其實解法類似。不過由於是環形,我們要在每次相加只有都對個數取余數,以防止數組越界。
不過,此處的j與前面(1)中的j意義並不一樣,此處的j意義為:從第i堆出發,往下數j堆石子。因此,j == 0時,自然dp[i][j] == 0了
因為j的意義不同,所以dp[i][j]長得自然不一樣了,實際上還是一個意思。
所以sum[i][j]也改為:
附上求最小值的代碼:
1 #include <iostream> 2 #include <algorithm> 3 #include <limits> 4 using namespace std; 5 const int INF = INT_MAX; 6 const int maxn = 1003; 7 int dp[maxn][maxn]; 8 int stone[maxn]; 9 int sum[maxn]; //0 - i 的和 10 int n; //本次的個數 11 int getSum(int i, int j) { 12 if (i + j >= n) 13 return getSum(i, n - i - 1) + getSum(0, i + j - n); 14 else 15 return sum[i + j] - (i > 0 ? sum[i - 1] : 0); 16 } 17 int findMin() { 18 for (int i = 0; i < n; i++) { 19 dp[i][0] = 0; 20 } 21 for (int j = 1; j < n; j++) { 22 for (int i = 0; i < n; i++) { 23 dp[i][j] = INF; 24 25 for (int k = 0; k < j; k++) { 26 dp[i][j] = min(dp[i][j], dp[i][k] + dp[(i + k + 1) % n][j - k - 1] + getSum(i, j)); 27 } 28 } 29 } 30 return dp[0][n - 1]; 31 } 32 33 int main() { 34 while (cin >> n) { 35 for (int i = 0; i < n; i++) { 36 cin >> stone[i]; 37 sum[i] = 0; 38 } 39 sum[0] = stone[0]; 40 for (int i = 1; i < n; i++) { 41 sum[i] = sum[i - 1] + stone[i]; 42 } 43 cout << findMin() << endl; 44 } 45 return 0; 46 }
Vane_Tse On the Road. 2014-07-10 10:04:43