切棍子的最小成本问题(限定每次切割必须切成整数长度)
题目描述
你有一根长度为 n 的木棍,它需要被切成若干段整数长度的小段。给定一个整数数组 cuts,其中每个元素表示一个必须进行切割的位置(距离木棍左端的距离,也是整数),并且 1 <= cuts[i] <= n-1(保证不会在端点切割)。每次切割的成本等于当前被切割木棍的长度。
你每次可以选择一根当前已有的木棍(可能是初始木棍,也可能是切割后得到的某一段),在其某个整数位置(该位置必须在 cuts 中,且尚未在该段上执行过该位置的切割)进行切割,将其分成两段整数长度的木棍。你需要按照某种顺序执行所有给定的切割(每个切割位置必须恰好被切割一次),目标是求出完成所有切割的最小总成本。
示例
-
输入:
n = 7,cuts = [1, 3, 4, 5] -
解释:木棍长度 7,需要在位置 1、3、4、5 处切割(位置从木棍左端算起,距离为整数)。
-
一种切割顺序:
- 先在位置 4 切,当前木棍长度 7,成本 7,得到两段长度分别为 4 和 3。
- 在左段(长度 4)的位置 1 切,成本 4,得到长度 1 和 3。
- 在右段(长度 3)的位置 3(相对原木棍)其实是该段的右端点?注意:我们需要在原木棍的绝对位置 3 和 5 处切,但此时位置 3 在左段(原位置 3 落在第一段长度 4 内,相对该段位置是 3 - 0 = 3? 不对,因为第一次切在 4,原位置 3 在左段(0~4)内,距离左段左端是 3,所以在该段切位置 3 是对的。但注意该段长度是 4,位置 3 是其内部一个整数点)
为了清晰,我们换一个顺序:
先排序cuts为[1, 3, 4, 5],并在两端添加 0 和 7,得到[0, 1, 3, 4, 5, 7]。
任何相邻两点之间的长度就是最终的一段。
问题转化为:按某种顺序合并这些相邻点之间的区间(最终每段长度已知),每次合并相邻区间的成本等于合并后区间的长度(即右端点 - 左端点),求将所有区间合并成一个(即原木棍)的最小成本?
不,正好相反:我们需要把[0,7]这个区间通过切割(即划分)成[0,1],[1,3],[3,4],[4,5],[5,7]这些小段,每次切割的成本是当前区间的长度,求最小总成本。
这等价于:把切割点排序后,考虑区间[l, r]代表木棍上从位置 l 到位置 r 的一段,如果我们需要在这个区间内部进行所有落在 (l, r) 内的切割,那么选择第一个切割点 k,成本为r-l,然后递归处理[l, k]和[k, r]。
所以这其实就是经典的“切棍子”问题,只不过切割点必须是指定的整数位置,且每次切割成本是当前段长度。
-
输出:
16
解释:一种最优切割顺序:- 切位置 4(成本 7),分成 [0,4] 和 [4,7]。
- 切位置 1(在 [0,4] 段,成本 4),分成 [0,1] 和 [1,4]。
- 切位置 3(在 [1,4] 段,成本 3),分成 [1,3] 和 [3,4]。
- 切位置 5(在 [4,7] 段,成本 3),分成 [4,5] 和 [5,7]。
总成本 = 7 + 4 + 3 + 3 = 17?这是 17,但示例输出是 16,说明有更优顺序。
实际上正确顺序: - 切位置 3(成本 7)→ [0,3] 和 [3,7]
- 切位置 1(在 [0,3],成本 3)→ [0,1] 和 [1,3]
- 切位置 4(在 [3,7],成本 4)→ [3,4] 和 [4,7]
- 切位置 5(在 [4,7],成本 3)→ [4,5] 和 [5,7]
总成本 = 7 + 3 + 4 + 3 = 17?还是 17。
等等,我们再算一个顺序: - 切位置 5(成本 7)→ [0,5] 和 [5,7]
- 切位置 4(在 [0,5],成本 5)→ [0,4] 和 [4,5]
- 切位置 3(在 [0,4],成本 4)→ [0,3] 和 [3,4]
- 切位置 1(在 [0,3],成本 3)→ [0,1] 和 [1,3]
总成本 = 7 + 5 + 4 + 3 = 19。
那么最小成本 16 怎么来的?
我们看官方示例:n=7, cuts=[1,3,4,5] 答案是 16。
最优顺序之一: - 切位置 4(成本 7)→ [0,4], [4,7]
- 切位置 2?不对,cuts 只有 1,3,4,5。
我们换个思路:将 cuts 排序并加上边界点 0 和 n,得到数组 arr=[0,1,3,4,5,7]。
区间长度依次是 1,2,1,1,2。
问题等价于:每次选择相邻两个区间合并,合并成本等于合并后区间长度(即两区间长度和),直到只剩一个区间(长度 7),求最小合并成本。
这是经典的石子合并问题(相邻合并,成本为两堆石子数量之和,求最小总成本)。
计算最小合并成本:
(1) 合并 1 和 2(成本 3)→ 得到 3,序列变为 [3,1,1,2]
(2) 合并 1 和 1(成本 2)→ 得到 2,序列变为 [3,2,2]
(3) 合并 2 和 2(成本 4)→ 得到 4,序列变为 [3,4]
(4) 合并 3 和 4(成本 7)→ 得到 7
总成本 = 3 + 2 + 4 + 7 = 16。
对应切割顺序(逆序看合并顺序):
合并 3 和 4 对应最早切割分开它们的位置,即位置 3 切割(因为 3 是 arr[2],分开区间 [0,3] 和 [3,7])成本 7。
合并 2 和 2 对应在 [3,7] 内部切位置 5(分开 [3,5] 和 [5,7])成本 4(此时段长度是 4?注意此时段是 [3,7] 长度 4,切位置 5 后分成 [3,5] 长度 2 和 [5,7] 长度 2)。
合并 1 和 1 对应在 [0,3] 内部切位置 1(分开 [0,1] 和 [1,3])成本 3。
合并 1 和 2 对应在 [3,5] 内部切位置 4(分开 [3,4] 和 [4,5])成本 2。
总成本 = 7 + 4 + 3 + 2 = 16。
注意这里合并顺序对应的切割顺序不是唯一的,但总成本最小是 16。
所以这个问题可以转化为石子合并问题。
解题思路
我们定义 arr 为排序后的切割点加上左右端点:arr = [0] + sorted(cuts) + [n]。
设 m = len(arr) - 1,即最终有 m 段木棍(arr[i] 到 arr[i+1] 长度记为 len[i])。
我们的目标是通过一系列的“切割”逆操作(即合并相邻段)来还原成一根完整木棍,每次合并的成本是合并后区间的长度。
令 dp[i][j] 表示合并第 i 段到第 j 段(共 j-i+1 段)成为一根连续木棍(即区间 [arr[i], arr[j+1]])的最小成本。
则转移方程:
dp[i][j] = min(dp[i][k] + dp[k+1][j]) + (arr[j+1] - arr[i]),其中 k 从 i 到 j-1 枚举分割点,表示最后一步合并是合并 [i,k] 和 [k+1,j] 两段,合并成本为当前区间长度 arr[j+1] - arr[i]。
初始条件:dp[i][i] = 0(单段无需合并)。
最终答案:dp[0][m-1]。
为什么可以这样建模
因为每次切割的成本是当前木棍长度,等价于在最终所有段合并的过程中,每次合并的成本是合并后区间的长度。合并的顺序正好对应切割的逆序,总成本相同。所以求最小切割成本转化为求最小合并成本,这就是标准的区间DP石子合并问题。
详细步骤
-
预处理
将cuts排序,并构建数组arr = [0] + sorted(cuts) + [n]。
令m = len(arr) - 1,即最终段数。 -
DP数组定义
dp[i][j]表示将arr[i..j+1]这些点之间的所有段(即第 i 段到第 j 段)合并成一根木棍的最小成本。其中0 <= i <= j <= m-1。
注意:第 i 段对应区间[arr[i], arr[i+1]],长度为arr[i+1]-arr[i]。 -
状态转移
考虑最后一步合并:一定是把[i..k]和[k+1..j]两段合并,合并成本为当前整段长度arr[j+1] - arr[i]。
所以dp[i][j] = min_{i<=k<j} (dp[i][k] + dp[k+1][j]) + (arr[j+1] - arr[i])。 -
计算顺序
按区间长度从小到大计算:
length = 2到m(表示合并的段数),对于每个长度,枚举起点i,计算j = i + length - 1,再枚举分割点k。 -
答案
dp[0][m-1]即为所求。
示例计算(n=7, cuts=[1,3,4,5])
-
排序 cuts → [1,3,4,5],arr = [0,1,3,4,5,7],m=5(段数 5)。
段长:1,2,1,1,2。 -
初始化
dp[i][i] = 0。 -
计算长度 2 的区间:
dp[0][1]:合并段0和段1(区间[0,1]和[1,3]),arr[2]-arr[0]=3-0=3,dp[0][0]+dp[1][1]+3 = 0+0+3 = 3。dp[1][2]:合并段1和段2(区间[1,3]和[3,4]),arr[3]-arr[1]=4-1=3,成本 3。dp[2][3]:arr[4]-arr[2]=5-3=2,成本 2。dp[3][4]:arr[5]-arr[3]=7-4=3,成本 3。
-
长度 3 的区间:
dp[0][2]:区间 [0,4],长度 4。
k=0:左dp[0][0]=0,右dp[1][2]=3,和=3,加 4 得 7。
k=1:左dp[0][1]=3,右dp[2][2]=0,和=3,加 4 得 7。
取 min=7。dp[1][3]:区间 [1,5],长度 4。
k=1:0+dp[2][3]=2 → 总 6。
k=2:dp[1][2]=3+0=3 → 总 7。
min=6。dp[2][4]:区间 [3,7],长度 4。
k=2:0+dp[3][4]=3 → 7。
k=3:dp[2][3]=2+0=2 → 6。
min=6。
-
长度 4 的区间:
dp[0][3]:区间 [0,5],长度 5。
k=0:0+dp[1][3]=6 → 11。
k=1:dp[0][1]=3+dp[2][3]=2 → 5+5=10。
k=2:dp[0][2]=7+0=7 → 12。
min=10。dp[1][4]:区间 [1,7],长度 6。
k=1:0+dp[2][4]=6 → 12。
k=2:dp[1][2]=3+dp[3][4]=3 → 6+6=12。
k=3:dp[1][3]=6+0=6 → 12。
min=12。
-
长度 5 的区间:
dp[0][4]:区间 [0,7],长度 7。
k=0:0+dp[1][4]=12 → 19。
k=1:dp[0][1]=3+dp[2][4]=6 → 9+7=16。
k=2:dp[0][2]=7+dp[3][4]=3 → 10+7=17。
k=3:dp[0][3]=10+dp[4][4]=0 → 10+7=17。
min=16。
得到答案 16,与示例一致。
复杂度分析
- 时间复杂度:O(m³),其中 m = len(cuts) + 1,因为区间长度最多 m 段。
- 空间复杂度:O(m²)。
关键点
- 将切割点排序并添加端点,转化为相邻合并问题。
- 切割成本等于当前段长,合并成本等于合并后区间长度,两者等价。
- 直接套用石子合并的区间DP模板即可。
这样,我们就完成了“切棍子的最小成本问题(限定每次切割必须切成整数长度)”的详细讲解。