0%

1000.合并石头的最低成本

1000.合并石头的最低成本

题目描述:

有 $n$ 堆石头排成一排,第 $i$ 堆中有 $stones[i]$ 块石头。

每次移动(move)需要将连续的 $k$ 堆石头合并为一堆,而这个移动的成本为这 $K$ 堆石头的总数。

找出把所有石头合并成一堆的最低成本。如果不可能,返回 $-1$ 。

数据范围:
$1\le n \le 30;2\le k\le 30$

题解:

与石子合并比较类似,石子合并是区间 $dp$ 做法。递推方程为

其中 $sum(i,k,k+1,j)$ 为合并两堆石子的代价。

使用相同的策略,用 $dp[i][j]$ 表示将区间 $[i,j]$ 中的石子合并之后的最小花费。但是这个合并并不一定能够合并成一堆,可以认为是合并到不能合并为止的最小花费。

$dp[i][j]$ 需要从 $dp[i][k]$ 和 $dp[k + 1][j]$ 转移过来,合并时如果左右两边的还未合并的小堆数量加起来超过了 $k$ ,那么就会产生新的花费。也就是说假如 $k = 3$ ,如果左右两侧分别为 $2$ 堆,那么就会产生新的花费。但是这个合并的代价与左侧一堆,右侧3堆合并的代价相同。因此可以只用枚举 $p = i + m\times(k-1)$ ,只枚举左侧合并为一堆,右侧其他堆的情况就行。

无解的情况,每次合并减少 $k - 1$ 堆,也就是 $n - m\times(k - 1) = 1$ ,即 $(n - 1)\mod (k - 1) = 0$ 是合法的,其他都是非法的。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Solution
{
public:
const static int maxn = 1e5 + 10;
const static int maxm = 1e5 + 10;
const static int INF = 0x3f3f3f3f;
int dp[31][31] = {};
int mergeStones(vector<int> &stones, int k)
{
int n = stones.size();
if (n < k || (n - 1) % (k - 1))
return -1;
vector<int> sum(n, 0);
sum[0] = stones[0];
memset(dp, 0x3f, sizeof(dp));
dp[0][0] = 0;
for (int i = 1; i < n; ++i)
{
sum[i] = sum[i - 1] + stones[i];
dp[i][i] = 0;
}
for (int len = 2; len <= n; ++len)
{
for (int i = 0; i + len - 1 < n; ++i)
{
int j = i + len - 1;
for (int p = i; p < j; p += k - 1)
{
dp[i][j] = min(dp[i][j], dp[i][p] + dp[p + 1][j] + (!((len - 1) % (k - 1))) * (sum[j] - (i - 1 >= 0 ? sum[i - 1] : 0)));
}
}
}
return dp[0][n - 1];
}
};