合并石子问题的三维扩展:带颜色标记的最小合并成本
问题描述
给定一个长度为 n 的数组 stones,表示 n 堆石子。每堆石子有一个颜色,颜色用一个整数表示,取值范围是 1 到 C。同时,给定一个二维数组 mergeCost,其维度为 C x C。mergeCost[a][b] 表示将一堆颜色为 a 的石子和一堆颜色为 b 的石子合并成一堆时所需消耗的成本。合并后的新石堆,其颜色由另一个 C x C 的数组 newColor 决定,即 newColor[a][b] 表示合并颜色 a 和颜色 b 的石堆后,新石堆的颜色。
每次合并操作,你只能合并相邻的两堆石子。合并后,原来的两堆石子消失,新生成的一堆石子出现在它们原来的位置,数组长度减少 1。重复此过程,直到只剩下一堆石子。
你的目标是,找出一个合并顺序,使得将所有石子合并为一堆的总成本最小,并返回这个最小总成本。
例子:
输入:
n = 4, C = 3
stones = [2, 1, 3, 2] (颜色数组)
mergeCost = [[0, 5, 2],
[5, 0, 3],
[2, 3, 0]]
newColor = [[2, 3, 1],
[3, 1, 2],
[1, 2, 3]]
一种可能的合并顺序:
- 合并位置1和2的石子(颜色1和3),成本 = mergeCost[1][3] = 2,新颜色 = newColor[1][3] = 1。数组变为 [2, 1, 2](颜色)。
- 合并位置0和1的石子(颜色2和1),成本 = mergeCost[2][1] = 5,新颜色 = newColor[2][1] = 3。数组变为 [3, 2]。
- 合并最后两堆(颜色3和2),成本 = mergeCost[3][2] = 3,新颜色 = newColor[3][2] = 1。总成本 = 2+5+3 = 10。
我们需要找到成本最小的合并顺序。
解题过程循序渐进讲解
这是一个经典的区间动态规划问题,但标准石子合并只关心石子数量(或重量),这里增加了“颜色”属性,并且合并成本和结果颜色由被合并的两堆颜色共同决定。这使得状态定义和转移更加复杂。
第一步:定义DP状态
在标准区间DP中,我们通常定义 dp[i][j] 为将区间 [i, j] 内的所有石子合并成一堆的最小成本。
但在本问题中,仅仅知道最小成本不够,因为对于同一个区间 [i, j],合并成一堆后,这一堆的颜色可能是多种多样的,而不同的最终颜色会影响后续与区间外石子合并的成本。
因此,我们需要增加一个维度来记录最终颜色。
定义:
dp[i][j][c] = 将区间 [i, j] 内的所有石子合并成一堆,且这堆石子的颜色为 c 所需的最小成本。
如果无法将区间 [i, j] 合并成一堆颜色为 c 的石子,则值为无穷大 (INF)。
其中:
- 0 <= i <= j < n
- 1 <= c <= C
第二步:分析状态转移
我们如何计算 dp[i][j][c] 呢?
考虑区间 [i, j]。为了将其合并成一堆颜色为 c 的石子,最后一次合并操作一定是将区间内的某两堆(它们分别是区间 [i, k] 和 [k+1, j] 的合并结果)合并成最终的一堆。
假设:
- 左半部分区间 [i, k] 合并成了一堆,颜色为
cL。 - 右半部分区间 [k+1, j] 合并成了一堆,颜色为
cR。 - 将颜色为
cL和cR的两堆合并,根据规则,合并成本是mergeCost[cL][cR],合并后新堆的颜色是newColor[cL][cR]。 - 我们希望最终颜色
c等于newColor[cL][cR]。
那么,完成这个合并方案的总成本是:
cost_left + cost_right + mergeCost[cL][cR]
其中 cost_left 是将 [i, k] 合并成颜色 cL 的最小成本,即 dp[i][k][cL]。
cost_right 是将 [k+1, j] 合并成颜色 cR 的最小成本,即 dp[k+1][j][cR]。
为了得到 dp[i][j][c],我们需要:
- 遍历所有可能的分割点 k (i <= k < j)。
- 对于每个 k,遍历所有可能的左半部分最终颜色 cL (1..C) 和右半部分最终颜色 cR (1..C)。
- 检查条件:
newColor[cL][cR] == c。 - 如果条件满足,则计算候选成本:
dp[i][k][cL] + dp[k+1][j][cR] + mergeCost[cL][cR]。 dp[i][j][c]取所有候选成本中的最小值。
第三步:初始化(单个石堆的区间)
对于区间长度为1的情况,即 i == j。
- 区间内只有一堆石子,其颜色是固定的,为
stones[i]。 - 所以,对于颜色 c:
- 如果 c == stones[i],那么
dp[i][i][c] = 0,因为不需要任何合并,它已经是一堆颜色为 c 的石子。 - 如果 c != stones[i],那么
dp[i][i][c] = INF,因为无法改变单个石堆的颜色。
- 如果 c == stones[i],那么
第四步:计算顺序
动态规划的计算顺序需要确保在计算大区间时,其依赖的所有小区间都已经计算完毕。
典型的区间DP计算顺序是:按区间长度从小到大的顺序。
- 先计算所有长度为1的区间(初始化)。
- 然后计算长度为2的区间,接着长度3,...,直到长度为 n。
对于每个区间 [i, j],我们遍历所有可能的分割点 k,以及所有可能的颜色组合 (cL, cR)。
第五步:最终答案
最终,我们需要将整个区间 [0, n-1] 合并成一堆。但是题目没有要求最终的颜色是什么。所以,最终答案应该是:
min( dp[0][n-1][c] ),其中 c 从 1 到 C 遍历。即,将所有可能最终颜色的最小成本再取最小值。
第六步:算法复杂度分析
- 状态数量:O(n² * C)
- 对于每个状态 dp[i][j][c],我们需要:
- 遍历分割点 k: O(n)
- 遍历左颜色 cL: O(C)
- 遍历右颜色 cR: O(C)
- 总时间复杂度:O(n³ * C³)。在 n 和 C 都不太大的情况下(比如 n <= 50, C <= 5),这是可行的。如果 C 较大,则需要优化。
第七步:示例推演(结合题目例子)
我们用例子来验证思路。为了简化,我们用颜色编号 1,2,3。
stones = [2, 1, 3, 2]
mergeCost 和 newColor 如上。
初始化 dp[i][i][c]:
- dp[0][0][2]=0, 其他颜色为INF。
- dp[1][1][1]=0, 其他为INF。
- dp[2][2][3]=0, 其他为INF。
- dp[3][3][2]=0, 其他为INF。
计算长度2的区间,例如 [0,1] (颜色2和1):
要计算 dp[0][1][c]。
遍历分割点 k=0(只有一种分割方式,因为长度2只有一种合并顺序)。
左区间[0,0]最终颜色必须是2,右区间[1,1]最终颜色必须是1。
合并成本 = mergeCost[2][1] = 5。
新颜色 = newColor[2][1] = 3。
所以,dp[0][1][3] 的一个候选值是 0+0+5=5。
检查其他颜色c?只有 newColor[2][1] 等于3,所以 dp[0][1][1] 和 dp[0][1][2] 没有合法转移,保持INF。
最终 dp[0][1][3]=5。
类似地计算其他长度2的区间。
最终计算整个区间 [0,3] 时,会考虑各种合并顺序和中间颜色组合,取最小值。手动计算可能较繁琐,但算法框架能保证找到最优解。
总结
本题是石子合并问题的三维DP扩展,核心在于状态中需要记录合并后的颜色,因为合并成本和结果颜色依赖于被合并两堆的颜色。通过定义 dp[i][j][c],并按照区间长度递增的顺序计算,我们可以系统地找出最小总成本。最终答案是对最后一个区间的所有可能颜色c取最小值。