问题描述:
给定一段长度为n的钢条和一个价格表Pi(i=1, 2, ……n),求钢条切割方案,使得销售收益r最大。注意,如果长度为n英寸的钢条的价格Pn足够大,最优解可能完全不需要切割。
问题分析:
根据问题描述可以得知,每段钢条的最大收益来自两种情况,切割和不切割。切割情况下,不同长度的钢条又可以切割成不同段,切割后的每段收益也存在两种情况(切割或不切割)。因此原问题的最优解由一个个子问题的最优解构成。
如下例所示:
长度 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|
价格 | 1 | 5 | 8 | 9 | 10 | 17 | 17 | 20 | 24 | 30 |
r=1,切割方案1=1(无切割)
r=5,切割方案2=2(无切割)
r=8,切割方案3=3(无切割)
r=10,切割方案4=2+2
r=13,切割方案5=2+3
r=17,切割方案6=6(无切割)
r=18,切割方案7=1+6或7=2+2+3
r=22,切割方案8=2+6
r=25,切割方案9=3+6
r=30,切割方案10=10(无切割)
通过上例分析,我们可以将原问题划分为一个个子问题,子问题通过比较不同切割方案和不切割方案的收益从而得到子问题的最优解,进一步得到原问题的最优解。
问题求解:
分治求解:
#include<iostream>
using namespace std;
int cut_rod(int *p, int n){
if(n == 0) return 0;
int q = -1e7;
for(int i = 1; i <= n; i++){
q = max(q, cut_rod(p, n-i)+p[i]);
}
return q;
}
int main(){
int p[11] = {0, 1, 5, 8, 9, 10, 17, 17, 20, 24, 30};
cout << cut_rod(p, 4);
return 0;
}
上述递归求解过程如下:
上述代码存在一个bug,当n大于给定存储价格表的数组长度时,数据会出错。
观察上述递归调用过程图,可以发现程序执行过程中存在大量的重复计算,因此分治方法的求解过程显然效率低下。
动态规划求解:
上述求解过程中,因为存在大量重复的运算所以造成程序执行效率低下。而动态规划求解过程通过对子问题解的保存,可以避免大量重复运算。
法一:
自顶向下求解,但保存每次的运算结果:
int r[1010];
void init_array(){
for(int i = 0; i < 1010; i++)
r[i] = -1;
}
int cut_rod_up(int *p, int n){
if(r[n] >= 0) return r[n];
int q;
if(n == 0) q = 0;
else q = -1;
for(int i = 1; i <= n; i++){
q = max(q, cut_rod_up(p, n-i)+p[i]);
}
r[n] = q;
return q;
}
通过r数组记录子问题的解,从而避免了大量的重复运算。
法二:
自底向上求解:
int cut_rod_down(int *p, int n){
r[0] = 0;
for(int i = 1; i <= n; i++){
int q = -1e7;
for(int j = 1; j <= i; j++)
q = max(q, p[j]+r[i-j]);//i-j+j = i;求解子问题
r[i] = q;
}
return r[n];
}
求解具体切割长度:
int s[1010];
int cut_rod_down(int *p, int n){
r[0] = 0;
for(int i = 1; i <= n; i++){
int q = -1e7;
for(int j = 1; j <= i; j++)
if(q < p[j]+r[i-j]){
q = p[j]+r[i-j];
s[i] = j;
}
r[i] = q;
}
return r[n];
}
打印具体长度:
while(n > 0){//n为钢条初始长度
cout << s[n];
n -= s[n];
}