dp-钢条切割
钢条切割
问题描述
Serling公司购买长钢条,将其切割为短钢条出售。假设切割工序没有成本,不同长度的钢条的售价如下:
length | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|
price | 1 | 5 | 8 | 9 | 10 | 17 | 17 | 20 | 24 | 30 |
那么钢条切割问题就是:给定一段长度为n英尺的钢条和一个价格表为pi(i=1...n),求切割钢条方案,使得销售收益最大(单位为元)。注意:如果长度为n英尺的钢条的价格pn足够大,那么最优解就是不需要切割。
问题分析
考虑 n = 4 的情况,那么有以下几种切割方式:
1.切割为四段,长度为:1,1,1,1;总共卖41=4元。
2.切割为三段,长度为:1,1,2;总共卖21+15=7元。
3.切割为两段,长度为:1,3;总共卖11+18=9元。
4.切割为两段,长度为:2,2;总共卖25=10元。
5.不切割,长度为:4;总共卖1*9=9元。
长度为n的钢条,总共有 \(2^{n-1}\) 种不同的切割方案,因为长度为n的钢条,总共有 n-1 个缝隙,每个缝隙都可以选择切或不切,所以有 \(2^{n-1}\) 种不同切割方案。所以随着n增大,切割方案总数呈指数级上升,遍历是不现实的。
在这里,很容易想到,当要分析长度为n的钢条的最优解时,可以先将钢条切成两段。将长度为n的钢条随意切割的方案是 \(2^{n-1}\) 种,但是只切两段的方案只有 n-1 种,这样规避了指数级计算量。
将切成的两段,分别再当作子问题去求解,这就是如下分治策略解法。
思路及简化
自顶向下递归
CUT-ROD(p, n)
if n == 0
return 0
q = -1
for i = 1 to n
q = max(q, p[i] + CUT-ROD(p, n-i))
return q
带备忘录的递归
MEMOIZED-CUT-ROD(p, n)
let r[0..n] be a new array
for i = 0 to n
r[i] = -1
return MEMOIZED-CUT-ROD-AUX(p, n, r)
MEMOIZED-CUT-ROD-AUX(p, n, r)
if r[n] >= 0
return r[n]
if n == 0
q = 0
else
q = -1
for i = 1 to n
q = max(q, p[i] + MEMOIZED-CUT-ROD-AUX(p, n-i, r))
r[n] = q
return q
自底向上
BOTTOM-UP-CUT-ROD(p, n)
let r[0..n] be a new array
r[0] = 0
for j = 1 to n
q = -1
for i = 1 to j
q = max(q, p[i] + r[j-i])
r[j] = q
return r[n]
程序
#include <iostream>
#include <new>
#define N 10
int solve(const int price[N], int* amount, int* cut, int len);
void print_resolution(const int* amount, const int* cut, int len);
int main(int argc, char** argv) {
int price[N]{1,5,8,9,10,17,17,20,24,30};
int len = 50;
// amount 存放最大金额
int* amount = new int[len]{0};
// cut 存放分割长度,print_resolution 中针对每个长度计算了分割方案
int* cut = new int[len]{0};
int ret = solve(price, amount, cut, len);
std::cout << "len: " << len << "; result: " << ret << std::endl;
print_resolution(amount, cut, len);
delete[] amount;
delete[] cut;
return 0;
}
void print_resolution(const int* amount, const int* cut, int len) {
// cut 中存放的分割长度不超过10,切割掉cut[i]长度后,剩余的部分需要迭代访问cut[rest]
// 例:长度23的分割方案 = cut[22] + 剩余 = 3 + cut[22-3] = 3 + cut[19]
// = 3 + 10 + cut[19-10] = 3 + 10 + cut[9] = 3 + 10 + 10
int j, rest;
for (int i = 0; i < len; ++i) {
std::cout << "len: " << i+1 << "; amount[i]: " << amount[i]
<< "; cut[i]: " << cut[i] << "\t";
rest = i + 1;
while (rest > 0) {
j = cut[rest-1] == 0 ? rest : cut[rest-1];
rest -= j;
std::cout << j << " ";
}
std::cout << std::endl;
}
}
int solve(const int price[N], int* amount, int* cut, int len) {
int half, sum;
cut[0] = 0;
for (int i = 0; i < len; ++i) {
if (i < N) amount[i] = price[i];
half = (i+1) / 2;
for (int j = 1; j <= half; ++j) {
sum = amount[i-j] + amount[j-1];
if (amount[i] < sum) {
amount[i] = sum;
cut[i] = j;
}
}
}
return amount[len-1];
}
测试:
$ g++ -o cut cut.cpp && ./cut
len: 50; result: 150
len: 1; amount[i]: 1; cut[i]: 0 1
len: 2; amount[i]: 5; cut[i]: 0 2
len: 3; amount[i]: 8; cut[i]: 0 3
len: 4; amount[i]: 10; cut[i]: 2 2 2
len: 5; amount[i]: 13; cut[i]: 2 2 3
len: 6; amount[i]: 17; cut[i]: 0 6
len: 7; amount[i]: 18; cut[i]: 1 1 6
len: 8; amount[i]: 22; cut[i]: 2 2 6
len: 9; amount[i]: 25; cut[i]: 3 3 6
len: 10; amount[i]: 30; cut[i]: 0 10
len: 11; amount[i]: 31; cut[i]: 1 1 10
len: 12; amount[i]: 35; cut[i]: 2 2 10
len: 13; amount[i]: 38; cut[i]: 3 3 10
len: 14; amount[i]: 40; cut[i]: 2 2 2 10
len: 15; amount[i]: 43; cut[i]: 2 2 3 10
len: 16; amount[i]: 47; cut[i]: 6 6 10
len: 17; amount[i]: 48; cut[i]: 1 1 6 10
len: 18; amount[i]: 52; cut[i]: 2 2 6 10
len: 19; amount[i]: 55; cut[i]: 3 3 6 10
len: 20; amount[i]: 60; cut[i]: 10 10 10
len: 21; amount[i]: 61; cut[i]: 1 1 10 10
len: 22; amount[i]: 65; cut[i]: 2 2 10 10
len: 23; amount[i]: 68; cut[i]: 3 3 10 10
len: 24; amount[i]: 70; cut[i]: 2 2 2 10 10
len: 25; amount[i]: 73; cut[i]: 2 2 3 10 10
len: 26; amount[i]: 77; cut[i]: 6 6 10 10
len: 27; amount[i]: 78; cut[i]: 1 1 6 10 10
len: 28; amount[i]: 82; cut[i]: 2 2 6 10 10
len: 29; amount[i]: 85; cut[i]: 3 3 6 10 10
len: 30; amount[i]: 90; cut[i]: 10 10 10 10
len: 31; amount[i]: 91; cut[i]: 1 1 10 10 10
len: 32; amount[i]: 95; cut[i]: 2 2 10 10 10
len: 33; amount[i]: 98; cut[i]: 3 3 10 10 10
len: 34; amount[i]: 100; cut[i]: 2 2 2 10 10 10
len: 35; amount[i]: 103; cut[i]: 2 2 3 10 10 10
len: 36; amount[i]: 107; cut[i]: 6 6 10 10 10
len: 37; amount[i]: 108; cut[i]: 1 1 6 10 10 10
len: 38; amount[i]: 112; cut[i]: 2 2 6 10 10 10
len: 39; amount[i]: 115; cut[i]: 3 3 6 10 10 10
len: 40; amount[i]: 120; cut[i]: 10 10 10 10 10
len: 41; amount[i]: 121; cut[i]: 1 1 10 10 10 10
len: 42; amount[i]: 125; cut[i]: 2 2 10 10 10 10
len: 43; amount[i]: 128; cut[i]: 3 3 10 10 10 10
len: 44; amount[i]: 130; cut[i]: 2 2 2 10 10 10 10
len: 45; amount[i]: 133; cut[i]: 2 2 3 10 10 10 10
len: 46; amount[i]: 137; cut[i]: 6 6 10 10 10 10
len: 47; amount[i]: 138; cut[i]: 1 1 6 10 10 10 10
len: 48; amount[i]: 142; cut[i]: 2 2 6 10 10 10 10
len: 49; amount[i]: 145; cut[i]: 3 3 6 10 10 10 10
len: 50; amount[i]: 150; cut[i]: 10 10 10 10 10 10