合并相邻石子堆的最小总成本(每次合并得分等于两堆石子数之和的立方)
我将为你讲解一个关于石子合并的区间动态规划问题。这个问题是经典石子合并问题的一个变体,其中合并两堆石子的得分不是简单的两堆石子数之和,而是其和的立方。这会导致状态转移时,合并得分不再是简单的加法,而是需要计算立方值,从而影响最优决策。
问题描述
我们有 N 堆石子排成一行,每堆石子有一定的数量(正整数)。我们希望通过一系列合并操作,将所有石子合并为一堆。每次合并操作只能合并相邻的两堆石子,合并的得分等于这两堆石子的数量之和的立方。目标是找到一种合并顺序,使得总得分最小(即最小化所有合并操作的得分总和)。
例如,石子堆的数量数组为 [1, 2, 3]。一种可能的合并顺序是:
- 合并前两堆 (1+2)³ = 27,剩下 [3, 3]。
- 合并剩下的两堆 (3+3)³ = 216,总得分 27+216=243。
但我们希望找到总得分最小的合并顺序。
解题思路
这是一个典型的区间动态规划问题,我们可以定义状态 dp[l][r] 表示将第 l 堆到第 r 堆石子(闭区间)合并为一堆的最小总得分。我们的最终目标是 dp[1][N]。
由于每次合并相邻两堆,我们可以考虑最后一次合并发生在位置 k,即先将区间 [l, k] 合并为一堆,再将区间 [k+1, r] 合并为一堆,然后合并这两堆。因此,状态转移方程的核心思想是:
dp[l][r] = min{ dp[l][k] + dp[k+1][r] + (sum(l,k) + sum(k+1,r))³ } 对于所有 k ∈ [l, r-1]
这里 sum(l, r) 表示区间 [l, r] 内石子的总数量。由于合并成两堆后,这两堆的数量分别是 sum(l, k) 和 sum(k+1, r),最后合并这两堆的得分就是它们和的立方。
我们可以预处理前缀和数组 prefix,使得 sum(l, r) = prefix[r] - prefix[l-1],这样可以在 O(1) 时间内计算区间和。
详细步骤
-
定义状态:
- 设
dp[i][j]表示合并第 i 堆到第 j 堆石子(1 ≤ i ≤ j ≤ N)的最小总得分。
- 设
-
初始化:
- 当区间长度为 1 时(即 i == j),不需要合并,得分为 0。所以
dp[i][i] = 0。
- 当区间长度为 1 时(即 i == j),不需要合并,得分为 0。所以
-
状态转移:
- 对于区间 [l, r],我们枚举分割点 k(l ≤ k < r),表示最后一步合并是将 [l, k] 合并成的一堆与 [k+1, r] 合并成的一堆进行合并。
- 左区间合并的最小得分为
dp[l][k],右区间为dp[k+1][r]。 - 最后合并的得分为
(sum(l,k) + sum(k+1,r))³,即(sum(l, r))³。注意,实际上左堆数量为sum(l,k),右堆为sum(k+1,r),它们的和就是整个区间的总石子数sum(l, r)。 - 所以转移方程为:
dp[l][r] = min{ dp[l][k] + dp[k+1][r] } + (sum(l, r))³ 其中 k 从 l 遍历到 r-1。 - 这里关键点是:无论怎么分割,最后一步的合并得分只取决于整个区间的总石子数,与分割点 k 无关。但不同的 k 会影响
dp[l][k] + dp[k+1][r]的值,我们需要选择最小的那个。
-
计算顺序:
- 区间 DP 通常按区间长度从小到大计算。先计算所有长度为 2 的区间,然后长度 3,直到长度 N。
-
答案:
- 最终答案是
dp[1][N]。
- 最终答案是
为什么立方会导致不同的最优解?
在经典的石子合并问题中(得分是两堆石子数之和),最优合并顺序通常与数组顺序有关,但得分函数是线性的。而这里得分是和的立方,这意味着合并两个大堆的代价会非常高(因为立方增长很快)。因此,算法会倾向于尽早合并较小的堆,以避免在最后合并两个大堆时产生巨大的立方代价。状态转移中的 min 操作会自动寻找这种策略。
示例演示
假设石子堆为 [1, 2, 3]。
- 前缀和: prefix[0]=0, prefix[1]=1, prefix[2]=3, prefix[3]=6。
- 区间长度 1: dp[1][1]=0, dp[2][2]=0, dp[3][3]=0。
- 长度 2:
- dp[1][2]: k=1, 左堆 sum(1,1)=1, 右堆 sum(2,2)=2, 总 sum=3, 得分=3³=27。dp[1][2] = 0+0+27 = 27。
- dp[2][3]: k=2, 总 sum=2+3=5, 得分=125。dp[2][3] = 0+0+125 = 125。
- 长度 3: dp[1][3]
- k=1: 左堆 sum(1,1)=1, 右堆 sum(2,3)=5, 总 sum=6, 得分=216。dp[1][1]+dp[2][3]+216 = 0+125+216 = 341。
- k=2: 左堆 sum(1,2)=3, 右堆 sum(3,3)=3, 总 sum=6, 得分=216。dp[1][2]+dp[3][3]+216 = 27+0+216 = 243。
- 取最小值 243。
所以最小总得分为 243,与之前例子一致(对应合并顺序:先合并前两堆,再合并结果与第三堆)。
复杂度分析
- 状态数 O(N²)。
- 每个状态需要枚举分割点 k,O(N)。
- 总时间复杂度 O(N³),空间复杂度 O(N²)(可以使用滚动数组优化空间,但这里不必要)。
核心要点
- 区间 DP 是解决合并类问题的标准方法。
- 状态定义要清晰:
dp[l][r]表示合并该区间的最小总得分。 - 状态转移时,注意最后一步合并的得分计算:由于是两堆合并,得分取决于这两堆石子数的和,而这两堆正好是左右子区间的总石子数,所以最终合并得分就是整个区间总石子数的立方。
- 由于立方函数增长迅速,算法会自然倾向于让大堆尽可能晚合并(但实际上我们求最小值,所以会尽可能让大堆“晚”形成,从而减少立方代价)。
这个问题的变体(比如得分是平方、四次方等)都可以用类似方法解决,只需修改最后合并时的代价函数即可。