动态规划——完全背包

基础知识点

        首先设置一个二维数组dp[][],令dp[i][j]表示前i个物品装进容量为j的背包能获得的最大价值。通过设置这么一个二维数组,dp[n][m]的值就是完全背包问题的解。

        只考虑第i件物品时,可将情况分为  是否放入第i件物品   两种:

  1. 对于容量为j的背包,如果不放入第i件物品,那么这个问题就转换成将前i-1个物品放入容量为j的背包的问题,即dp[i][j] = dp[i-1][j]
  2. 对于容量为j的背包,如果放入第i件物品,那么当前背包的容量就变成了j-w[i],并得到这个物品的价值v[i]。但是由于第i件物品仍然可以取,所以并不是转移到dp[i-1][j-w[i]],而是转移到dp[i][j-w[i]]之后这个问题就转化成将前i-1个物品放入容量为j-w[i]的背包问题,即dp[i][j] = dp[i][j-w[i]]+v[i]

        从以上两种情况可以得到状态转移方程:dp[i][j] = max(dp[i-1][j], dp[i][j-w[i]]+v[i])。转移时要注意j-w[i]的值是否为非负值,若为负则代表当前的容量无法放入第i件物品,不能进行转移。

        边界情况处理:dp[i][0] = dp[0][j] = 0  (0<=i<=n,  0<=j<=m)

        观察状态转移的特点,可以发现dp[i][j]的转移仅与dp[i][j-w[i]]和dp[i-1][j]有关,即仅与二维数组中本行的上一行有关。根据这个特点,可以将原本的二维数组优化为一维数组,并用如下的方式完成状态转移:dp[j] = max(dp[j], dp[j-w[i]]+v[i])。为了保证状态正确转移,必须保证在每次更新中确定状态dp[j]时,dp[j-w[i]]已被修改。这就需要在每次更新中,正序遍历所有j值。

延伸知识点

        求最优解的背包问题题目中,事实上有两种不太相同的问法。有的题目要求“恰好装满背包”时的最优解,有的题目则并没有要求必须把背包装满。一种区别这两种问法的实现方法是在初始化的时候有所不同

  1. 如果是第一种问法,要求恰好装满背包,那么在初始化时除了f[0]为0其它f[1..V]均设为-∞,这样就可以保证最终得到的f[N]是一种恰好装满背包的最优解。
  2. 如果并没有要求必须把背包装满,而是只希望价格尽量大,初始化时应该将f[0..V]全部设为0。

        为什么呢?可以这样理解:初始化的f数组事实上就是在没有任何物品可以放入背包时的合法状态。如果要求背包恰好装满,那么此时只有容量为0的背包可能被价值为0的nothing“恰好装满”,其它容量的背包均没有合法的解,属于未定义的状态,它们的值就都应该是-∞了。如果背包并非必须被装满,那么任何容量的背包都有一个合法解“什么都不装”,这个解的价值为0,所以初始时状态的值也就全部为0了。

例题——Piggy-Bank

代码

#include <iostream>
#include <climits>
using namespace std;
const int MAXN = 501;
const int MAXM = 10001;
const int INF = 5e8+1;
int main(){
    int T,E,F,N,P[MAXN],W[MAXN];
    long long dp[MAXM];
    cin>>T;
    while(T--){
        cin>>E>>F>>N;
        for(int i=1;i<=N;i++){
            cin>>P[i]>>W[i];
        }
        dp[0]=0;
        for(int i=1;i<=F-E;i++){
            dp[i] = INF;
        }
        for(int i=1;i<=N;i++){
            for(int j=W[i];j<=F-E;j++){
                dp[j] = min(dp[j],dp[j-W[i]]+P[i]);
            }
        }
        if(dp[F-E]==INF) cout<<"This is impossible."<<endl;
        else cout<<"The minimum amount of money in the piggy-bank is "<<dp[F-E]<<"."<<endl;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值