动态规划之切割钢条

问题描述:

给定一个数组p[i],表示钢条长度为i时,可以销售的价格。为了使一块钢条效益最大化,问应该怎样切割钢条?


解析:假设长度为4的钢条,可以有以下几种方案:1.不切割        2.切成1,3两段        3.切成2,2两段         4.切成3,1两段             5.切成1,1,2三段           6.切成1,2,1三段

7.切成2,1,1三段          8.切成1,1,1,1四段

因为每个单元长度可以选择且或者不切,所以总的方案数共有 2^(n-1) 种


我们用Rn表示长度为n的最大效益,很明显,我们归纳出状态转移方程: Rn = max(Pn,R1+Rn-1,R2+Rn-2,...,Rn-1+R1)

在这里,钢条切割问题满足最优子结构性质:问题的最优解由相关子问题的最优解组合而成,而这些子问题可以独立求解。

我们这里采用一个简化版本:

Rn = max(Pi + Rn-i) ,i从1到n

首先我们很容易想到的是,自顶向下递归实现

#include <iostream>
using namespace std;

int p[] = {1,5,8,9,10,17,17,20,24,30};

int cut_rod(int n){
    if(n == 0)
        return 0;
    int q = -1000;
    for(int i=1;i<=n;i++){
        q = max(q,p[i-1]+cut_rod(n-i));
    }
    return q;
}

int main(){
    int n;
    cin>>n;   //表示共有n段
    cout<<cut_rod(n)<<endl;
    return 0;
}



因为上述做法做了很多重复的工作,每次递归都重复计算了某些值,我们用动态规划两种方法进行优化 :

1.带备忘录的自顶向下法

此法仍然是按自然的递归进行,只不过过程中会保存每个子问题的解,在函数中首先会判断该子问题的解是否已经求过,就可以避免多次计算

#include <iostream>
#include <cstring>
using namespace std;
const int MAX = 20;  //表示最大的段数为20

int p[] = {1,5,8,9,10,17,17,20,24,30};
int r[MAX];

int cut_rod(int n){
    if(r[n] >= 0)
        return r[n];  //表示已经进行过一次操作了
    int q = -1000;
    if(n == 0)
        q = 0;
    else{
        for(int i=1;i<=n;i++){
            q = max(q,p[i-1]+cut_rod(n-i));
        }
    }
    r[n] = q;
    return q;
}

int main(){
    int n;
    cin>>n;
    for(int i=0;i<MAX;i++)
        r[i] = -1000;
    cout<<cut_rod(n)<<endl;
    return 0;
}



2.自底向上法

我们知道,任何子问题的解只依赖于更小的子问题的解。我们可以将子问题按规模排序,按由小到大的顺序进行求解。当求解每个子问题时,它所依赖的更小的问题已经求解过了。

#include <iostream>
#include <cstring>
using namespace std;
const int MAX = 20;

int p[] = {1,5,8,9,10,17,17,20,24,30};

int main(){
    int n;
    cin>>n;
    int r[MAX];
    memset(r,0,sizeof(r));
    for(int j = 1;j<=n;j++){
        int q = -1000;
        for(int i=1;i<=j;i++){
            q = max(q,p[i-1]+r[j-i]);
        }
        r[j] = q;
    }
    cout<<r[n]<<endl;
    return 0;
}

下面说一下,如果题目中要求我们不仅给出最大的收益,而且要给出最优的方案,那么我们只需要用一个数组保存最优解对应的第一段钢条的切割长度。

举例来说,比如我们要求的是的钢条长度为5,那么我们发现s[5]的值为2表示在第二段这里要切一下,然后看s[5-2]=s[3]=3表示剩余的长度为3的是一段

#include <iostream>
#include <cstring>
using namespace std;
const int MAX = 20;

int p[] = {1,5,8,9,10,17,17,20,24,30};

int main(){
    int n;
    cin>>n;
    int r[MAX];
    int s[MAX];  //s[i]表示第一段钢条的最优切割长度
    memset(r,0,sizeof(r));
    for(int j=1;j<=n;j++){
        int q = -1000;
        for(int i=1;i<=j;i++){
            if(q < p[i-1]+r[j-i]){
                q = p[i-1]+r[j-i];
                s[j] = i;
            }
        }
        r[j] = q;
    }
    cout<<"最佳效益为:"<<r[n]<<endl;
    while(n>0){
        cout<<s[n]<<" ";
        n = n - s[n];
    }
    return 0;
}

以上案例 参照《算法导论》第三版动态规划 P204-P210


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值