切棍子的最小成本问题(基础版)
字数 3064 2025-12-21 16:47:36

切棍子的最小成本问题(基础版)


题目描述

有一根长度为 \(n\) 单位的木棍,标记了从 1 到 n 的刻度位置(左端为 0,右端为 n)。
给你一个整数数组 cuts,其中 cuts[i] 表示需要在这根木棍上的某个位置进行切割的位置(1 ≤ cuts[i] ≤ n-1)。
每次切割的成本等于当前被切割木棍的长度。
请你返回将木棍按指定位置全部切开所需的最小总成本。

注意:

  • 每次只能选择一个位置进行切割,并且必须从该位置将当前木棍切成两根。
  • 切割顺序会影响总成本。
  • cuts 数组中的位置互不相同,并且已按升序排列(如果不是,可以预先排序)。
  • 木棍必须最终在 cuts 中所有位置都被切开。

示例

输入:
n = 7, cuts = [1, 3, 4, 5]
输出:16

解释:
一种最优的切割顺序是:

  1. 先在位置 3 切割,成本是 7(当前棍子长度为 7)。
  2. 然后在位置 5 切割(此时有两段:0-3 和 3-7,选择 3-7 这段切割位置 5),成本是 4(这段长度 4)。
  3. 接着在位置 1 切割(0-3 这段切割位置 1),成本是 3。
  4. 最后在位置 4 切割(3-5 这段切割位置 4),成本是 2。
    总成本 = 7 + 4 + 3 + 2 = 16。

为什么这是区间动态规划?

  • 切割会不断将一段木棍分成更小的两段。
  • 每段木棍的切割决策只依赖于这段的起点终点,以及这段内部的切割点。
  • 因此可以定义区间 dp[l][r] 表示在某个“虚拟木棍区间”上完成所有必须切割的最小成本。

但要注意:这里木棍的“区间”不是从 0 到 n 的原始位置,而是用 cuts 数组加上两端 0 和 n 构成的所有“关键点”的索引区间。


详细解题过程

1. 问题转换

原始木棍总长 n,必须切割的点是 cuts 里的位置。
我们先把 cuts 排序,然后在数组前后分别加入 0 和 n,得到一个新的数组 arr。
例如 n=7, cuts=[1,3,4,5] → 排序后就是 [1,3,4,5],加入两端 → arr = [0, 1, 3, 4, 5, 7]。
arr 的长度 m = len(cuts) + 2。
这样,arr[i] 就表示第 i 个关键点的位置。

问题转化为:
在区间 [arr[l], arr[r]] 这段木棍上,如果内部还有一些必须切割的点(即 arr 中索引在 (l, r) 之间的点),那么怎样切割成本最小。

为什么这样转换?
因为切割点都在 arr 中,任意一段木棍的长度 = arr[r] - arr[l],其内部需要切割的点就是 arr 索引在 l 和 r 之间的那些点。

2. 定义 dp 状态

令 dp[l][r] 表示在木棍区间 (arr[l], arr[r]) 这一段上,完成内部所有必须切割的最小成本。
注意:这里 l 和 r 是 arr 的索引,范围是 0 到 m-1。
最终答案是 dp[0][m-1],即整个木棍。

特别规定:如果区间 (l, r) 内部没有必须切割的点(即 r = l+1),则 dp[l][r] = 0,因为这段不需要再切了。

3. 状态转移

对于 dp[l][r],假设我们在内部选择一个切割点 k,其中 l < k < r,表示我们在位置 arr[k] 处切一刀。
这一刀将区间分成两段:
左段对应 (l, k)
右段对应 (k, r)
切割这一刀的成本是当前这段木棍的长度:arr[r] - arr[l]。
之后还要分别处理左右两段,左段最小成本是 dp[l][k],右段最小成本是 dp[k][r]。
所以总成本是:

\[\text{cost} = (arr[r] - arr[l]) + dp[l][k] + dp[k][r] \]

我们要在 l 和 r 之间所有可能的 k 中,选一个使总成本最小的。

转移方程

\[dp[l][r] = \min_{l < k < r} \left( (arr[r] - arr[l]) + dp[l][k] + dp[k][r] \right) \]

如果区间内没有可选的 k(即 r == l+1),则 dp[l][r] = 0。

4. 递推顺序

区间 DP 常见顺序:按区间长度从小到大计算。
区间长度 len 从 2 到 m-1(因为 l 和 r 至少相差 2 才有内部切割点,len=2 时内部无点,dp=0 已初始化)。
对于每个长度 len,枚举左端点 l,右端点 r = l + len,然后枚举切割点 k 在 (l, r) 之间。

5. 算法步骤

  1. 排序 cuts(如果未排序)。
  2. 构建 arr = [0] + sorted(cuts) + [n]。
  3. 令 m = len(arr)。
  4. 初始化 dp 为 m×m 的二维数组,初始值为 0(因为当区间长度=2时 dp=0 是成立的,但其他情况后面计算会覆盖)。
  5. 对于长度 len 从 2 到 m-1:
    • 对于 l 从 0 到 m-1-len:
      • r = l + len
      • 如果 len == 2,dp[l][r] = 0(内部无切割点,可跳过,因为初始就是0)
      • 否则:
        • 初始化 dp[l][r] = 无穷大
        • 对于 k 从 l+1 到 r-1:
          • 当前成本 = (arr[r] - arr[l]) + dp[l][k] + dp[k][r]
          • 更新 dp[l][r] 为最小值
  6. 返回 dp[0][m-1]

举例验证

n=7, cuts=[1,3,4,5]
arr = [0,1,3,4,5,7] (m=6)

初始化 dp 6×6 全 0。

长度 len=3(区间内有一个切割点):

  • l=0, r=2 → arr[0]=0, arr[2]=3, 内部切割点 k=1 (arr[1]=1)
    dp[0][2] = (3-0) + dp[0][1] + dp[1][2] = 3 + 0 + 0 = 3
  • l=1, r=3 → (1,4) 长度 3, 内点 k=2 → dp=3
  • l=2, r=4 → (3,5) 长度 2, 内点 k=3 → dp=2
  • l=3, r=5 → (4,7) 长度 3, 内点 k=4 → dp=3
  • l=0, r=3 等更长区间后续会用到这些子结果。

继续计算 len=4,5,... 最终得到 dp[0][5] = 16,与示例一致。


复杂度分析

  • 时间复杂度:O(m³),其中 m = cuts.length + 2。因为三层循环:区间长度、左端点、切割点。
  • 空间复杂度:O(m²),存储 dp 表。

关键点小结

  1. 将 cuts 扩展为 arr 数组,将原问题转化为“在关键点序列的相邻区间上做区间 DP”。
  2. 状态 dp[l][r] 表示在木棍区间 (arr[l], arr[r]) 上完成所有内部切割的最小成本。
  3. 切割点 k 的选择对应将大区间分成两个小区间,加上当前段长度的成本。
  4. 按区间长度从小到大递推,确保子问题已解。

如果你理解了上述过程,可以尝试用代码实现,或者进一步思考:如果 cuts 数量很大(比如 100),O(m³) 可能较慢,是否存在优化方法?但在基础版中,这个解法是标准且清晰的。

切棍子的最小成本问题(基础版) 题目描述 有一根长度为 \( n \) 单位的木棍,标记了从 1 到 n 的刻度位置(左端为 0,右端为 n)。 给你一个整数数组 cuts,其中 cuts[ i] 表示需要在这根木棍上的某个位置进行切割的位置(1 ≤ cuts[ i ] ≤ n-1)。 每次切割的成本等于当前被切割木棍的长度。 请你返回将木棍按指定位置全部切开所需的最小总成本。 注意: 每次只能选择一个位置进行切割,并且必须从该位置将当前木棍切成两根。 切割顺序会影响总成本。 cuts 数组中的位置互不相同,并且已按升序排列(如果不是,可以预先排序)。 木棍必须最终在 cuts 中所有位置都被切开。 示例 输入: n = 7, cuts = [ 1, 3, 4, 5 ] 输出:16 解释: 一种最优的切割顺序是: 先在位置 3 切割,成本是 7(当前棍子长度为 7)。 然后在位置 5 切割(此时有两段:0-3 和 3-7,选择 3-7 这段切割位置 5),成本是 4(这段长度 4)。 接着在位置 1 切割(0-3 这段切割位置 1),成本是 3。 最后在位置 4 切割(3-5 这段切割位置 4),成本是 2。 总成本 = 7 + 4 + 3 + 2 = 16。 为什么这是区间动态规划? 切割会不断将一段木棍分成更小的两段。 每段木棍的切割决策只依赖于这段的 起点 和 终点 ,以及这段内部的切割点。 因此可以定义区间 dp[ l][ r ] 表示在某个“虚拟木棍区间”上完成所有必须切割的最小成本。 但要注意:这里木棍的“区间”不是从 0 到 n 的原始位置,而是用 cuts 数组加上两端 0 和 n 构成的所有“关键点”的索引区间。 详细解题过程 1. 问题转换 原始木棍总长 n,必须切割的点是 cuts 里的位置。 我们先把 cuts 排序,然后在数组前后分别加入 0 和 n,得到一个新的数组 arr。 例如 n=7, cuts=[ 1,3,4,5] → 排序后就是 [ 1,3,4,5],加入两端 → arr = [ 0, 1, 3, 4, 5, 7 ]。 arr 的长度 m = len(cuts) + 2。 这样,arr[ i ] 就表示第 i 个关键点的位置。 问题转化为: 在区间 [ arr[ l], arr[ r] ] 这段木棍上,如果内部还有一些必须切割的点(即 arr 中索引在 (l, r) 之间的点),那么怎样切割成本最小。 为什么这样转换? 因为切割点都在 arr 中,任意一段木棍的长度 = arr[ r] - arr[ l ],其内部需要切割的点就是 arr 索引在 l 和 r 之间的那些点。 2. 定义 dp 状态 令 dp[ l][ r] 表示在木棍区间 (arr[ l], arr[ r]) 这一段上,完成 内部所有必须切割 的最小成本。 注意:这里 l 和 r 是 arr 的索引,范围是 0 到 m-1。 最终答案是 dp[ 0][ m-1 ],即整个木棍。 特别规定 :如果区间 (l, r) 内部没有必须切割的点(即 r = l+1),则 dp[ l][ r ] = 0,因为这段不需要再切了。 3. 状态转移 对于 dp[ l][ r],假设我们在内部选择一个切割点 k,其中 l < k < r,表示我们在位置 arr[ k ] 处切一刀。 这一刀将区间分成两段: 左段对应 (l, k) 右段对应 (k, r) 切割这一刀的成本是当前这段木棍的长度:arr[ r] - arr[ l ]。 之后还要分别处理左右两段,左段最小成本是 dp[ l][ k],右段最小成本是 dp[ k][ r ]。 所以总成本是: \[ \text{cost} = (arr[ r] - arr[ l]) + dp[ l][ k] + dp[ k][ r ] \] 我们要在 l 和 r 之间所有可能的 k 中,选一个使总成本最小的。 转移方程 : \[ dp[ l][ r] = \min_ {l < k < r} \left( (arr[ r] - arr[ l]) + dp[ l][ k] + dp[ k][ r ] \right) \] 如果区间内没有可选的 k(即 r == l+1),则 dp[ l][ r ] = 0。 4. 递推顺序 区间 DP 常见顺序:按区间长度从小到大计算。 区间长度 len 从 2 到 m-1(因为 l 和 r 至少相差 2 才有内部切割点,len=2 时内部无点,dp=0 已初始化)。 对于每个长度 len,枚举左端点 l,右端点 r = l + len,然后枚举切割点 k 在 (l, r) 之间。 5. 算法步骤 排序 cuts(如果未排序)。 构建 arr = [ 0] + sorted(cuts) + [ n ]。 令 m = len(arr)。 初始化 dp 为 m×m 的二维数组,初始值为 0(因为当区间长度=2时 dp=0 是成立的,但其他情况后面计算会覆盖)。 对于长度 len 从 2 到 m-1: 对于 l 从 0 到 m-1-len: r = l + len 如果 len == 2,dp[ l][ r ] = 0(内部无切割点,可跳过,因为初始就是0) 否则: 初始化 dp[ l][ r ] = 无穷大 对于 k 从 l+1 到 r-1: 当前成本 = (arr[ r] - arr[ l]) + dp[ l][ k] + dp[ k][ r ] 更新 dp[ l][ r ] 为最小值 返回 dp[ 0][ m-1 ] 举例验证 n=7, cuts=[ 1,3,4,5 ] arr = [ 0,1,3,4,5,7 ] (m=6) 初始化 dp 6×6 全 0。 长度 len=3 (区间内有一个切割点): l=0, r=2 → arr[ 0]=0, arr[ 2]=3, 内部切割点 k=1 (arr[ 1 ]=1) dp[ 0][ 2] = (3-0) + dp[ 0][ 1] + dp[ 1][ 2 ] = 3 + 0 + 0 = 3 l=1, r=3 → (1,4) 长度 3, 内点 k=2 → dp=3 l=2, r=4 → (3,5) 长度 2, 内点 k=3 → dp=2 l=3, r=5 → (4,7) 长度 3, 内点 k=4 → dp=3 l=0, r=3 等更长区间后续会用到这些子结果。 继续计算 len=4,5,... 最终得到 dp[ 0][ 5 ] = 16,与示例一致。 复杂度分析 时间复杂度:O(m³),其中 m = cuts.length + 2。因为三层循环:区间长度、左端点、切割点。 空间复杂度:O(m²),存储 dp 表。 关键点小结 将 cuts 扩展为 arr 数组,将原问题转化为“在关键点序列的相邻区间上做区间 DP”。 状态 dp[ l][ r] 表示在木棍区间 (arr[ l], arr[ r ]) 上完成所有内部切割的最小成本。 切割点 k 的选择对应将大区间分成两个小区间,加上当前段长度的成本。 按区间长度从小到大递推,确保子问题已解。 如果你理解了上述过程,可以尝试用代码实现,或者进一步思考:如果 cuts 数量很大(比如 100),O(m³) 可能较慢,是否存在优化方法?但在基础版中,这个解法是标准且清晰的。