原題:
有 N 堆石頭排成一排,第 i 堆中有 stones[i] 塊石頭。
每次移動(move)需要將連續的 K 堆石頭合併爲一堆,而這個移動的成本爲這 K 堆石頭的總數。
找出把所有石頭合併成一堆的最低成本。如果不可能,返回 -1 。
示例 1:
輸入:stones = [3,2,4,1], K = 2
輸出:20
解釋:
從 [3, 2, 4, 1] 開始。
合併 [3, 2],成本爲 5,剩下 [5, 4, 1]。
合併 [4, 1],成本爲 5,剩下 [5, 5]。
合併 [5, 5],成本爲 10,剩下 [10]。
總成本 20,這是可能的最小值。
示例 2:
輸入:stones = [3,2,4,1], K = 3
輸出:-1
解釋:任何合併操作後,都會剩下 2 堆,我們無法再進行合併。所以這項任務是不可能完成的。.
示例 3:
輸入:stones = [3,5,1,2,6], K = 3
輸出:25
解釋:
從 [3, 5, 1, 2, 6] 開始。
合併 [5, 1, 2],成本爲 8,剩下 [3, 8, 6]。
合併 [3, 8, 6],成本爲 17,剩下 [17]。
總成本 25,這是可能的最小值。
提示:
1 <= stones.length <= 30
2 <= K <= 30
1 <= stones[i] <= 100
代碼:
class Solution {
public:
int dp[51][51][51];
int mergeStones(vector<int>& stones, int K)
{
int n = stones.size();
if ((n - K) % (K - 1))
return -1;
vector<int> tmp(stones.size() + 1, 0);
for (int i = 0; i < stones.size(); i++)
tmp[i + 1] = stones[i];
memset(dp, 0x3f, sizeof(dp));
vector<int> sum(stones.size()+1,0);
for (int i = 1; i <= n; i++)
{
dp[i][i][1] = 0;
sum[i] = sum[i - 1] + tmp[i];
}
for (int len = 2; len <= n; len++)
{
for (int i = 1; i <= n - len + 1; i++)
{
int j = i + len - 1;
for (int x = i; x < j; x++)
{
for (int k = 2; k <= len; k++)
{
dp[i][j][k] = min(dp[i][j][k], dp[i][x][k - 1] + dp[x + 1][j][1]);
}
}
dp[i][j][1] = min(dp[i][j][1], dp[i][j][K] + sum[j] - sum[i - 1]);
}
}
return dp[1][n][1];
}
};
思路:
原始的石子合併問題是兩個兩個合併,此題多加了一個條件K,要求一次合併K堆石子。
石子合併問題可以用表示區間i到j這些石子合併後得到的最小決策值,如果是兩個石子合併,那麼在決策狀態中,如果是在第k個位置尋找子區間。那麼
表示區間i到j石子兩兩合併所做的決策狀態。
如果是三個石子呢?設置表示區間i到j之間三個石子三個石子合併得到的最優解。
同理可得
這裏需要枚舉與來尋找最優解
可想而知,如果是K很大的話,基本上沒有辦法做到對狀態劃分的枚舉。
這裏增加維度來表示區間i到j之間的狀態,題目中限定了合併石子個數K爲變量。
那麼很容易想到設定狀態來表示一個區間的石子合併後得到的最優值。
如果將上面的狀態解釋爲在區間i到區間j使用連續k個石子合併規則得到的最優值會有一個問題,如何建立狀態轉移呢?即,如何從兩個石子合併的狀態得到三個石子合併的狀態呢?很顯然做不到。
換一種想法考慮
設置表示爲將區間i到j的石子合併爲k堆的最小代價
那麼即k-1堆石子與1堆石子合併得到k堆石子,可以實現遞推操作
合併的方式表現在狀態轉移的過程中,每次將得到的合併,得到將K堆石子合併後得到的最優值。