钢条切割问题是一个经典的动态规划问题,旨在通过切割钢条获得最大收益。以下是详细解释和解决方案:
问题描述
- 给定长度为
n的钢条和价格表p,其中p[i]表示长度为i的钢条的价格(i = 1, 2, ..., n)。 - 目标:找到切割方案,使销售收益最大化。
动态规划解法
核心思想
- 最优子结构 :长度为
n的钢条的最优解包含子问题的最优解(例如,切割成两段后,每段的最优解)。 - 递归关系 :
-
设
r[i]为长度为i的钢条的最大收益。 -
初始值:
r[0] = 0(长度为 0 的钢条收益为 0)。 -
递推公式:
r[i] = max_{1≤j≤min(i, m)} { p[j] + r[i - j] }
其中
m是价格表的最大长度(即m = len(p)),j是第一段切割的长度。
-
算法步骤
-
初始化:
- 创建数组
r[0..n]存储最大收益,r[0] = 0。 - 创建数组
s[0..n]存储切割方案,s[i]记录长度为i时第一段切割的长度。
- 创建数组
-
自底向上计算:
- 遍历长度
i从 1 到n:- 初始化
q = -∞(临时变量,存储当前最大收益)。 - 遍历切割长度
j从 1 到i:- 若
j超出价格表范围(j > m),跳过。 - 否则,计算收益
temp = p[j] + r[i - j]。 - 若
temp > q,更新q = temp并记录s[i] = j。
- 若
- 设置
r[i] = q。
- 初始化
- 遍历长度
-
回溯切割方案:
- 从
i = n开始:- 输出
s[i](第一段切割长度)。 - 更新
i = i - s[i],重复直到i = 0。
- 输出
- 从
时间复杂度和空间复杂度
- 时间复杂度 :
O(n^2)(双重循环)。 - 空间复杂度 :
O(n)(存储r和s数组)。
示例演示
假设 n = 4,价格表 p = [0, 1, 5, 8, 9](索引从 1 开始):
- 计算过程 :
i=1:j=1→r[1] = p[1] + r[0] = 1,s[1]=1i=2:j=1→1 + r[1]=2;j=2→5 + r[0]=5→r[2]=5,s[2]=2i=3:j=3→8 + r[0]=8→r[3]=8,s[3]=3i=4:j=2→5 + r[2]=10→r[4]=10,s[4]=2
- 切割方案 :
4 → 2 + 2(收益5 + 5 = 10)。
**代码实现(Python)
python
def rod_cutting(n, p):
m = len(p) - 1 # 价格表最大长度(索引从1开始)
r = [-10**18] * (n + 1) # 最大收益数组
s = [0] * (n + 1) # 切割方案数组
r[0] = 0 # 初始化
# 动态规划填表
for i in range(1, n + 1):
q = -10**18
for j in range(1, i + 1):
if j > m: # j超出价格表范围
continue
temp = p[j] + r[i - j]
if temp > q:
q = temp
s[i] = j
r[i] = q
# 回溯切割方案
solution = []
i = n
while i > 0:
solution.append(s[i])
i -= s[i]
return r[n], solution
示例
n = 4
p = [0, 1, 5, 8, 9] # p[0]无效,p[1]=1, p[2]=5, ...
max_profit, cuts = rod_cutting(n, p)
print("最大收益:", max_profit) # 输出: 10
print("切割方案:", cuts) # 输出: [2, 2]
关键点
- 价格表处理 :索引从 1 开始,
p[j]直接对应长度j的价格。 - 边界处理 :当
j > m时跳过(长度超出价格表范围)。 - 方案回溯 :利用
s数组逐步还原切割段。
此方法高效地解决了钢条切割问题,适用于任意长度的钢条和价格表。