带权重的石子合并(最大化乘积和)
题目描述
有 n 堆石子排成一列,每堆石子有重量 w[i](w[i] > 0)。每次操作可以合并相邻的两堆石子,合并的得分为这两堆石子的重量乘积。合并后,新的一堆石子的重量为原来两堆石子重量之和。重复合并直到只剩下一堆石子。要求设计一种合并顺序,使得总得分最大,并输出这个最大总得分。
例如,石子重量数组为 [3, 4, 2, 5]:
- 一种合并顺序:先合并 4 和 2(得分 4×2 = 8),得到
[3, 6, 5];再合并 3 和 6(得分 3×6 = 18),得到[9, 5];最后合并 9 和 5(得分 9×5 = 45),总得分 = 8 + 18 + 45 = 71。 - 另一种顺序:先合并 3 和 4(得分 12),得到
[7, 2, 5];再合并 2 和 5(得分 10),得到[7, 7];最后合并 7 和 7(得分 49),总得分 = 12 + 10 + 49 = 71。
需要找到所有顺序中的最大总得分。
注意:合并顺序不同会导致总得分不同,目标是最优化合并顺序。
解题思路分析
这个问题属于区间动态规划的经典变体。在普通石子合并问题中,合并得分通常是两堆石子重量之和,而这里是两堆石子重量的乘积。这导致合并的价值不仅依赖于当前两堆的重量,还影响后续合并的基数(因为新堆重量是两堆之和)。
关键观察:
- 最后一次合并的得分是最后两堆的重量乘积。这两堆各自是原序列某个连续子区间合并的结果。
- 大区间的最优解依赖于其划分成两个小区间的最优解。
- 状态设计:
dp[l][r]表示将区间[l, r]内的石子合并为一堆时,能获得的最大总得分。但注意合并需要知道区间内石子的总重量(因为新堆重量为两子区间总重量之和),因此还需要维护区间和。
详细解题步骤
步骤 1:定义状态
- 设
dp[l][r]表示将区间[l, r]内的石子合并成一堆的过程中,能获得的最大总得分(不包括合并成最终一堆之后的操作,因为已经是一堆了,不再有合并得分)。 - 设
sum[l][r]表示区间[l, r]内石子的总重量。为方便,可用前缀和数组preSum快速计算:sum[l][r] = preSum[r] - preSum[l-1]。
注意:这里 dp[l][r] 的定义是“合并区间 [l, r] 成为一堆的过程中的最大得分”,那么最后一步合并是将左右两堆合并成一堆,此时得分是 sum[left] * sum[right],其中 left 和 right 是左右两堆的重量,也就是左右子区间的总重量。
步骤 2:状态转移方程
考虑区间 [l, r] 如何通过一次合并得到最终一堆。最后一次合并一定是将 [l, k] 合并成的一堆与 [k+1, r] 合并成的一堆进行合并,其中 k 是分割点(l ≤ k < r)。
合并过程:
- 左子区间
[l, k]合并成一堆,总重量为sum[l][k],此过程最大得分为dp[l][k]。 - 右子区间
[k+1, r]合并成一堆,总重量为sum[k+1][r],此过程最大得分为dp[k+1][r]。 - 最后将左右两堆合并,得分为
sum[l][k] * sum[k+1][r]。
因此,区间 [l, r] 的最大得分是:
dp[l][r] = max{ dp[l][k] + dp[k+1][r] + sum[l][k] * sum[k+1][r] },其中 l ≤ k < r
边界条件:当 l == r 时,区间只有一堆石子,不需要合并,得分为 0,即 dp[l][l] = 0。
步骤 3:计算顺序
由于大区间依赖小区间,我们需要按照区间长度从小到大进行动态规划。
- 先计算长度为 1 的区间(得分均为 0)。
- 再计算长度为 2、3、…、n 的区间。
具体循环:
- 外层循环:区间长度
len从 2 到 n。 - 内层循环:左端点
l从 1 到n - len + 1,右端点r = l + len - 1。 - 在内层循环中,枚举分割点
k从l到r-1,更新dp[l][r]。
步骤 4:前缀和预处理
为了快速得到区间和,预处理前缀和数组 prefix,其中 prefix[i] 表示前 i 堆石子的总重量(下标从 1 开始)。
sum[l][r] = prefix[r] - prefix[l-1]。
步骤 5:最终答案
整个区间 [1, n] 合并成一堆的最大得分就是 dp[1][n]。
举例说明
以石子重量 [3, 4, 2, 5] 为例(n=4)。
前缀和:prefix = [0, 3, 7, 9, 14]。
-
初始化:
dp[i][i] = 0。 -
长度 len=2:
[1,2]:k=1,dp[1][2] = dp[1][1] + dp[2][2] + sum[1][1]*sum[2][2] = 0 + 0 + 3*4 = 12。[2,3]:k=2,dp[2][3] = 0 + 0 + 4*2 = 8。[3,4]:k=3,dp[3][4] = 0 + 0 + 2*5 = 10。
-
长度 len=3:
[1,3]:k=1:dp[1][1] + dp[2][3] + sum[1][1]*sum[2][3] = 0 + 8 + 3*(4+2) = 8 + 18 = 26。k=2:dp[1][2] + dp[3][3] + sum[1][2]*sum[3][3] = 12 + 0 + (3+4)*2 = 12 + 14 = 26。- 取 max = 26。
[2,4]:k=2:dp[2][2] + dp[3][4] + sum[2][2]*sum[3][4] = 0 + 10 + 4*(2+5) = 10 + 28 = 38。k=3:dp[2][3] + dp[4][4] + sum[2][3]*sum[4][4] = 8 + 0 + (4+2)*5 = 8 + 30 = 38。- 取 max = 38。
-
长度 len=4:
[1,4]:k=1:dp[1][1] + dp[2][4] + sum[1][1]*sum[2][4] = 0 + 38 + 3*(4+2+5) = 38 + 33 = 71。k=2:dp[1][2] + dp[3][4] + sum[1][2]*sum[3][4] = 12 + 10 + (3+4)*(2+5) = 22 + 49 = 71。k=3:dp[1][3] + dp[4][4] + sum[1][3]*sum[4][4] = 26 + 0 + (3+4+2)*5 = 26 + 45 = 71。- 取 max = 71。
最终答案:dp[1][4] = 71。
算法复杂度
- 状态数:O(n²)。
- 每个状态枚举分割点:O(n)。
- 总时间复杂度:O(n³)。
- 空间复杂度:O(n²) 用于存储
dp数组。
为什么这样设计是合理的?
因为在最后一次合并时,左右两堆的重量是它们对应区间的总重量,乘积就是最后一次合并的得分。而左右子区间内部的合并得分已经由子问题的最优解给出。这样,通过枚举所有可能的分割点,我们可以保证找到全局最优的合并顺序。
思考扩展
如果题目改为最小化总得分,状态转移方程中的 max 改为 min 即可。
如果石子排列成环形,只需将原数组复制一份接在后面,然后对每个长度为 n 的区间求 dp[i][i+n-1],取最大值,复杂度升为 O((2n)³) = O(8n³),可用四边形不等式优化到 O(n²)(但此题乘积形式不一定满足单调性,一般仍用 O(n³))。
希望这个分步讲解能帮助你理解这个区间动态规划问题。如果有不清楚的步骤,欢迎继续提问。