好的,我注意到你的已讲题目列表非常详尽。为了避免重复,我从区间动态规划的经典题库中,挑选一个尚未出现过的题目。这个题目不仅考察基础的区间定义,还涉及对区间内“状态”的建模,是一个很好的进阶练习。
区间动态规划例题:带权值的石子合并(最大化总价值,且每次合并成本与区间总质量相关)
题目描述
假设有一排 n 堆石子,排成一个数组 stones[0..n-1]。每堆石子有两个属性:
- 质量
m[i](正整数)。 - 价值
v[i](整数,可正可负)。
游戏规则如下:
- 你每次可以选择相邻的两堆石子
i和i+1进行合并。 - 合并后,原来的两堆消失,新生成的一堆会放在它们原来的位置。
- 新堆的质量是原来两堆质量之和:
m_new = m[i] + m[i+1]。 - 新堆的价值是原来两堆价值之和:
v_new = v[i] + v[i+1]。 - 本次合并操作带来的 “收益” 计算公式为:
收益 = m_new * (v[i] + v[i+1])。注意,这里m_new是新堆的总质量,(v[i]+v[i+1])就是新堆的价值v_new。所以这个收益也可以理解为新堆质量 * 新堆价值。 - 游戏一直进行,直到将所有石子合并成一堆。
你的目标是:找到一种合并顺序,使得在整个合并过程中,所有单次合并的收益之和最大。你需要计算出这个最大的总收益。
输入示例:
n = 4
质量 m = [3, 2, 5, 4]
价值 v = [2, -1, 3, 2]
输出:一个整数,表示最大总收益。
解题思路分析
这个题目是“石子合并”问题的一个复杂变种。与经典的最小化合并代价不同,这里我们最大化收益,且收益的计算依赖于区间内石子的总质量和总价值。
核心挑战在于,当我们合并一个区间时,这个区间最终会变成一个“大堆”,这个大堆的质量和价值,是区间内所有原始堆的质量之和与价值之和。而我们在决定如何分割这个区间(即先合并哪两部分)时,需要知道左右两个子区间最终形成的“大堆”的质量和价值,才能计算合并它们时的收益。
1. 状态定义
我们需要一个状态来完整描述一个区间 [i, j] 经过一系列最优合并后形成的最终状态。
定义 dp[i][j] 为:将第 i 堆到第 j 堆石子(闭区间)合并成一堆后,所能获得的最大总收益。
但是,仅仅有最大收益还不够。为了计算更大区间的收益,我们还需要知道这个最终堆的总质量和总价值。因为它们是大区间合并时收益计算 (m_left + m_right) * (v_left + v_right) 的必备参数。
因此,我们需要扩展状态,同时存储两个信息:
sumM[i][j]:区间[i, j]内所有石子的总质量。这个值在游戏过程中是守恒的,无论怎么合并,最终堆的质量都是这个总和。所以我们可以预先计算前缀和来快速得到它。sumV[i][j]:区间[i, j]内所有石子的总价值。同样,这也是守恒的。
由于 sumM 和 sumV 可以独立计算,dp[i][j] 的状态转移就可以只聚焦于“最大收益”。
2. 状态转移方程
我们考虑大区间 [i, j] 的最后一次合并。它一定是由某个分界点 k (i <= k < j) 将区间分成左右两部分 [i, k] 和 [k+1, j],然后分别将这两部分合并成两堆,最后将这两堆合并。
设:
- 左区间
[i, k]合并后的最大收益为dp[i][k],总质量为sumM[i][k],总价值为sumV[i][k]。 - 右区间
[k+1, j]合并后的最大收益为dp[k+1][j],总质量为sumM[k+1][j],总价值为sumV[k+1][j]。
那么,将左右两堆最终合并的这一次操作,带来的收益是:
merge_gain = (sumM[i][k] + sumM[k+1][j]) * (sumV[i][k] + sumV[k+1][j])。
根据定义,sumM[i][k] + sumM[k+1][j] 正是整个大区间 [i, j] 的总质量 sumM[i][j]。
sumV[i][k] + sumV[k+1][j] 正是整个大区间 [i, j] 的总价值 sumV[i][j]。
所以,merge_gain = sumM[i][j] * sumV[i][j]。这是一个惊人的发现:最后一步合并的收益,只与整个区间的总质量和总价值有关,与如何分割(k的选择)无关!
因此,对于区间 [i, j] 的总收益 dp[i][j],其状态转移方程为:
dp[i][j] = max(dp[i][k] + dp[k+1][j]) + sumM[i][j] * sumV[i][j],其中 i <= k < j。
解释:dp[i][j] 等于所有可能的分割方式中,左右子区间自身内部合并的最大收益之和的最大值,再加上最后合并左右子区间的那一步固定收益。
3. 初始化
- 对于单堆石子(区间长度为1),不需要合并,所以收益为0。
dp[i][i] = 0,对于所有i。 sumM[i][i] = m[i]sumV[i][i] = v[i]
4. 计算顺序
典型的区间DP顺序:按区间长度从小到大计算。
- 先计算所有长度为1的区间(初始化)。
- 再计算长度为2的区间:
dp[i][i+1] = 0 + 0 + sumM[i][i+1] * sumV[i][i+1]。 - 然后计算长度为3,长度为4,...,直到长度为
n。
5. 最终答案
整个数组 [0, n-1] 合并成一堆的最大总收益就是 dp[0][n-1]。
详细解题步骤(以示例为例)
输入:
n = 4
m = [3, 2, 5, 4]
v = [2, -1, 3, 2]
步骤1:预处理总质量和总价值
我们可以用前缀和快速计算任意区间的 sumM 和 sumV。
prefixM = [3, 5, 10, 14] // 质量前缀和
prefixV = [2, 1, 4, 6] // 价值前缀和
sumM[i][j] = prefixM[j] - (i>0 ? prefixM[i-1] : 0)
sumV[i][j] = prefixV[j] - (i>0 ? prefixV[i-1] : 0)
为了清晰,我们手动列出所有区间:
sumM[0][0]=3,sumV[0][0]=2sumM[1][1]=2,sumV[1][1]=-1sumM[2][2]=5,sumV[2][2]=3sumM[3][3]=4,sumV[3][3]=2sumM[0][1]=5,sumV[0][1]=1sumM[1][2]=7,sumV[1][2]=2sumM[2][3]=9,sumV[2][3]=5sumM[0][2]=10,sumV[0][2]=4sumM[1][3]=11,sumV[1][3]=4sumM[0][3]=14,sumV[0][3]=6
步骤2:DP表初始化与计算
创建一个 n x n 的 dp 表,初始为0。
长度 L=1:
dp[0][0]=0, dp[1][1]=0, dp[2][2]=0, dp[3][3]=0
长度 L=2:
[0,1]:dp[0][1] = dp[0][0]+dp[1][1] + sumM[0][1]*sumV[0][1] = 0+0 + 5*1 = 5[1,2]:dp[1][2] = 0+0 + 7*2 = 14[2,3]:dp[2][3] = 0+0 + 9*5 = 45
长度 L=3:
[0,2]: 分界点k可以是0或1。k=0: 分成[0,0]和[1,2]。收益 =dp[0][0]+dp[1][2] + sumM[0][2]*sumV[0][2] = 0+14 + 10*4 = 54k=1: 分成[0,1]和[2,2]。收益 =dp[0][1]+dp[2][2] + 10*4 = 5+0 + 40 = 45- 取最大值
max(54, 45) = 54。所以dp[0][2] = 54。
[1,3]: 分界点k可以是1或2。k=1:[1,1]&[2,3]:0 + 45 + 11*4 = 45 + 44 = 89k=2:[1,2]&[3,3]:14 + 0 + 44 = 58- 取最大值
max(89, 58) = 89。所以dp[1][3] = 89。
长度 L=4:
[0,3]: 分界点k可以是0, 1, 2。k=0:[0,0]&[1,3]:0 + 89 + sumM[0][3]*sumV[0][3] = 89 + 14*6 = 89+84=173k=1:[0,1]&[2,3]:5 + 45 + 84 = 134k=2:[0,2]&[3,3]:54 + 0 + 84 = 138- 取最大值
max(173, 134, 138) = 173。所以dp[0][3] = 173。
步骤3:得出答案
最终答案 dp[0][3] = 173。
关键点总结与扩展思考
- 状态设计的技巧:本题的关键在于识别出,虽然我们需要知道子区间的质量和价值,但它们都是可加的区间属性,与合并顺序无关。因此可以用独立的前缀和数组(或区间DP时累加计算)来获取,从而让
dp状态只关注“最大收益”。 - 收益的独立性:最后一步合并的收益
sumM[i][j] * sumV[i][j]是一个常数,这在化简状态转移方程时起到了决定性作用。这使得问题变成了:寻找一个分割点,使得左右两个子区间内部合并的收益之和最大。 - 与经典石子合并的区别:经典问题是
dp[i][j] = min(dp[i][k] + dp[k+1][j]) + sumWeight[i][j],其中sumWeight[i][j]是区间总质量(代价因子)。在本问题中,sumM[i][j]*sumV[i][j]相当于那个“附加代价”,但它是加在最后,且计算方式不同。 - 算法复杂度:状态数
O(n^2),每个状态需要枚举O(n)个分割点,总时间复杂度为O(n^3),空间复杂度为O(n^2)。对于n在几百的量级是可行的。
你可以尝试修改题目,例如让合并收益的计算方式变成 (v_left + v_right) * (m_left - m_right),那么最后一步的收益就不再是常数,而是依赖于左右子区间的质量差,状态设计就需要相应地存储更多信息,难度会进一步提升。