[动态规划](烦人的背包)背包问题

背包问题:

描述
在n个物品中挑选若干物品装入背包,最多能装多满?假设背包的大小为m,每个物品的大小为A[i]
你不可以将物品进行切割。
样例
样例 1:
输入: [3,4,8,5], backpack size=10
输出: 9
样例 2:
输入: [2,3,5,7], backpack size=12
输出: 12

思路:

对于上面的问题,我们进行分析,以样例一为例:
背包大小为10,我们最多能装多满呢?
如果我们使用贪心算法的话,也就是说,只要背包有足够的空间能够容纳该物品,我们就放入背包;
第一个物品放入,背包还有7容量;
第二个物品放入,背包还有3容量;
第三个物品放入时,背包剩余容量不够了:这时候,我们需要考虑是不是要放入,如果放入背包的话,我们需要取出那些物品才能够将背包放的更满呢?
经过分析,我们可以判断,我们需要保存一些状态信息,来帮助我们判断是否放入物品到背包中及取出那些物品在放入背包中可以使背包更满,这些条件贪心算法是不能够完成的,我们刚才提到保存状态信息,所以一定有状态转移,所以我们使用动态规划是能够解决这个问题的

动态规划:

状态定义:
我们定义一个二维数组dp[ i][ j ] 表示前i个物品放入j大小的背包中,能装的最大状态
状态转移方程:
如果第i个物品的空间大于背包的空间,无论如何 该物品是不能放入背包中的 因此dp[ i][ j] =dp[i-1][j];
如果第i个物品的空间不大于背包的空间,是可以放入背包的,但是我们面临着两种选择,放或者不放
不放的话 dp[i][j]=dp[i-1][j]
放的话,背包至少要剩余物品空间的大小,因此我们需要找到这个大小的背包 j-A[i];
dp[i][j]= dp[i-1][j-A[i]] +A[i];
上面的两种情况下,我们只需要取最大的值,满足我们的状态定义
如果我们j为0,肯定放入的大小为0
如果i为0,表示没有放入任何物品,此时放入的大小也一定为0;

代码:

int backPack(int m, vector<int> &A) {
        // write your code here
        int sizeA=A.size();
        vector<vector<int>>  dp(sizeA+1,vector<int>(m+1));
        for(int i=1;i<=sizeA;++i)
        {
            for(int j=1;j<=m;++j)
            {
                if(A[i-1]>j)
                {
                    //物品大于背包容量,一定装不下
                    dp[i][j]=dp[i-1][j];
                }
                else
                {
                    //物品小于背包容量,一定能够装下物品
                    dp[i][j]=max(dp[i-1][j],dp[i-1][j-A[i-1]]+A[i-1]);
                }
            }
        }
        return dp[sizeA][m];
    }

代码优化:

在上面的代码中用二维数组存储状态,但是有些空间的浪费,因为我们每次都是一行一行的进行状态转移,因此我们可以将二维数组使用以为数组进行代替,这样可以节省空间,但是在进行状态转移的时候,我们大容量背包的状态一定是要从小容量的状态推到出来的,因此在循环的时候,我们要从大容量向小容量循环;优化代码如下:

  int backPack(int m, vector<int> &A) {
        // write your code here
        int sizeA=A.size();
        vector<int>  dp(m+1);
        for(int i=1;i<=sizeA;++i)
        {
            for(int j=m;j>=A[i-1];--j)
            {
                    dp[j]=max(dp[j],dp[j-A[i-1]]+A[i-1]);
            }
        }
        return dp[m];
    }

背包问题 II

描述
有 n 个物品和一个大小为 m 的背包. 给定数组 A 表示每个物品的大小和数组 V 表示每个物品的价值.
问最多能装入背包的总价值是多大?
A[i], V[i], n, m 均为整数
你不能将物品进行切分
你所挑选的要装入背包的物品的总大小不能超过 m
每个物品只能取一次
样例
样例 1:
输入: m = 10, A = [2, 3, 5, 7], V = [1, 5, 2, 4]
输出: 9
解释: 装入 A[1] 和 A[3] 可以得到最大价值, V[1] + V[3] = 9
样例 2:
输入: m = 10, A = [2, 3, 8], V = [2, 5, 8]
输出: 10
解释: 装入 A[0] 和 A[2] 可以得到最大价值, V[0] + V[2] = 10

思路:

解决此题,照旧动态规划:
状态定义:
我们定义一个二维数组dp[ i][ j ] 表示前i个物品放入j大小的背包中,装入背包的最大价值
状态转移方程:
如果第i个物品的空间大于背包的空间,无论如何 该物品是不能放入背包中的 因此dp[ i][ j] =dp[i-1][j];
如果第i个物品的空间不大于背包的空间,是可以放入背包的,但是我们面临着两种选择,放或者不放
不放的话 dp[i][j]=dp[i-1][j]
放的话,背包至少要剩余物品空间的大小,因此我们需要找到这个大小的背包 j-A[i];
dp[i][j]= dp[i-1][j-A[i]] +V[i];
上面的两种情况下,我们只需要取最大的值,满足我们的状态定义
如果我们j为0,肯定放入的大小为0
如果i为0,表示没有放入任何物品,此时放入的大小也一定为0;

对于上面的思想,我们可以使用以为数组进行空间优化,思想和背包问题类似;

代码

int backPackII(int m, vector<int> &A, vector<int> &V) {
        // write your code here
        vector<int> dp(m+1,0);
        int sizeA=A.size();
        for(int i=1;i<=sizeA;++i)
        {
            for(int j=m;j>=A[i-1];--j)
            {
                    dp[j]=max(dp[j],dp[j-A[i-1]]+V[i-1]);
               
            }
        }
        return dp[m];
    }

背包问题 III

描述
给定 n 种物品, 每种物品都有无限个. 第 i 个物品的体积为 A[i], 价值为 V[i].
再给定一个容量为 m 的背包. 问可以装入背包的最大价值是多少?
不能将一个物品分成小块.
放入背包的物品的总大小不能超过 m.
样例
样例 1:
输入: A = [2, 3, 5, 7], V = [1, 5, 2, 4], m = 10
输出: 15
解释: 装入三个物品 1 (A[1] = 3, V[1] = 5), 总价值 15.
样例 2:
输入: A = [1, 2, 3], V = [1, 2, 3], m = 5
输出: 5
解释: 策略不唯一. 比如, 装入五个物品 0 (A[0] = 1, V[0] = 1).

思路:

不用说,这个题目依旧动态规划,不过与上两道题目不同的是 每个物品有无限次,我们需要对此进行处理;
状态定义:
我们定义一个二维数组dp[ i][ j ] 表示前i个物品放入j大小的背包中,装入背包的最大价值
状态转移方程:
如果第i个物品的空间大于背包的空间,无论如何 该物品是不能放入背包中的 因此dp[ i][ j] =dp[i-1][j];
如果第i个物品的空间不大于背包的空间,是可以放入背包的,但是我们面临着两种选择,放或者不放
不放的话 dp[i][j]=dp[i-1][j]
放的话,背包至少要剩余物品空间的大小,因此我们需要找到这个大小的背包 j-A[i];
dp[i][j]= dp[i-1][j-A[i]] +V[i];
上面的两种情况下,我们只需要取最大的值,满足我们的状态定义
如果我们j为0,肯定放入的大小为0
如果i为0,表示没有放入任何物品,此时放入的大小也一定为0;

对于上面的思想,我们可以使用以为数组进行空间优化,思想和背包问题类似;

上面就是我们以往的思想,因为每个物品有无限个,因此在放入背包物品的时候,我们不再仅有一种选择,而是由多种选择:
dp[i][j]= dp[i-1][j-A[i]] +V[i];
dp[i][j]= dp[i-1][j-2*A[i]] +2 * V[i];
dp[i][j]= dp[i-1][j-3 * A[i]] +3 * V[i];
。。。。。。
直至背包装不下为止,背包三的改动也仅仅局限与此;

代码:

int backPackIII(vector<int> &A, vector<int> &V, int m) {
        // write your code here
        int sizeA=A.size();
        vector<int>  dp(m+1,0);

        for(int i=1;i<=sizeA;++i)
        {
            for(int j=m;j>=A[i-1];--j)
            {
                 int count=1;
                 while(count*A[i-1]<=j)
                 {
//我们可以采用一个循环实现无限次放入物品,直至放不下的情况,当然这是一种最直接的方法
                    dp[j]=max(dp[j],dp[j-count*A[i-1]]+count*V[i-1]);
                    ++count;
                 }        
            }
        }
        return dp[m];
    }
int backPackIII(vector<int> &A, vector<int> &V, int m) {
        // write your code here
        int sizeA=A.size();
        vector<int>  dp(m+1,0);
        for(int i=1;i<=sizeA;++i)
        {
            for(int j=A[i-1];j<=m;++j)
            {
            //与前面不同,用小状态更新后面的状态可以避免多次使用循环,提高了效率
                if(dp[j-A[i-1]]+V[i-1]>dp[j])
                {
                    dp[j]=dp[j-A[i-1]]+V[i-1];
                }
            }
        }
        return dp[m];
    }

背包问题 IV

描述
给出 n 个物品, 以及一个数组, nums[i]代表第i个物品的大小, 保证大小均为正数并且没有重复, 正整数 target 表示背包的大小, 找到能填满背包的方案数。
每一个物品可以使用无数次
样例
样例1
输入: nums = [2,3,6,7] 和 target = 7
输出: 2
解释:
方案有:
[7]
[2, 2, 3]
样例2
输入: nums = [2,3,4,5] 和 target = 7
输出: 3
解释:
方案有:
[2, 5]
[3, 4]
[2, 2, 3]

思路:

不用多说,依旧动态规划
状态定义:
我们定义一个二维数组dp[ i][ j ] 表示前i个物品放入j大小的背包中,放满背包的方案数
状态转移方程:
如果第i个物品的空间大于背包的空间,无论如何 该物品是不能放入背包中的 因此dp[ i][ j] =dp[i-1][j];
如果第i个物品的空间不大于背包的空间,物品是可以放入背包的,
但是如果要放满的话
dp[i][j]= dp[i-1][j-A[i]] ;
dp[i][j]= dp[i-1][j-2 * A[i]] ;
dp[i][j]= dp[i-1][j-3 * A[i]] ;
。。。
知道不能存放为止;
由于是求放满背包的数量,因此我们需要将上面的结果相加
同样,我们可以进行空间优化

代码:

int backPackIV(vector<int> &nums, int target) {
        // write your code here
        int size=nums.size();
        vector<int> dp(target+1,0);
        dp[0]=1;//如果背包大小为0的话,我们仅有一种装满的方法,即不妨任何东西
        for(int i=1;i<=size;++i)
        {
            for(int j=nums[i-1];j<=target;++j)
            {
                    dp[j]+=dp[j-nums[i-1]];
            }
        }
        return dp[target];
    }

背包问题 V

描述
给出 n 个物品, 以及一个数组, nums[i] 代表第i个物品的大小, 保证大小均为正数, 正整数 target 表示背包的大小, 找到能填满背包的方案数。
每一个物品只能使用一次
样例
给出候选物品集合 [1,2,3,3,7] 以及 target 7
结果的集合为:
[7]
[1,3,3]
返回 2

思路

本体的解决思路依旧是动态规划,需要注意的地方是每个物品只能使用一次,且物品重量有相同的可能,因此如果从小状态到大状态的转变时,有可能会出现冗余的情况,避免冗余结果的产生,我们需要从大状态转变为小状态

int backPackV(vector<int> &nums, int target) {
          // write your code here
        
        int size=nums.size();
        //进行部分解的优化,如果数组中素有物品极爱起来,都不能够装满背包,则一定装不满背包
        int sum=0;
        for(int i=0;i<size;++i)
        {
             sum+=nums[i];
        }
        if(sum<target)
        {
            return 0;
        }
        vector<int> dp(target+1,0);
        dp[0]=1;//如果背包大小为0的话,我们仅有一种装满的方法,即不妨任何东西
        for(int i=1;i<=size;++i)
        {
            for(int j=target;j>=nums[i-1];--j)
            {
                    dp[j]+=dp[j-nums[i-1]];
            }
        }
        return dp[target];
    }

如有错误,欢迎评论指正!!!
点击题目,即可转向OJ

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
买书问题 dp实现 题目:买书 有一书店引进了一套书,共有3卷,每卷书定价是60元,书店为了搞促销,推出一个活动,活动如下: 如果单独购买其中一卷,那么可以打9.5折。 如果同时购买两卷不同的,那么可以打9折。 如果同时购买三卷不同的,那么可以打8.5折。 如果小明希望购买第1卷x本,第2卷y本,第3卷z本,那么至少需要多少钱呢?(x、y、z为三个已知整数)。 1、过程为一次一次的购买,每一次购买也许只买一本(这有三种方案),或者买两本(这也有三种方案), 或者三本一起买(这有一种方案),最后直到买完所有需要的书。 2、最后一步我必然会在7种购买方案中选择一种,因此我要在7种购买方案中选择一个最佳情况。 3、子问题是,我选择了某个方案后,如何使得购买剩余的书能用最少的钱?并且这个选择不会使得剩余的书为负数 。母问题和子问题都是给定三卷书的购买量,求最少需要用的钱,所以有"子问题重叠",问题中三个购买量设置为参数, 分别为i、j、k。 4、的确符合。 5、边界是一次购买就可以买完所有的书,处理方式请读者自己考虑。 6、每次选择最多有7种方案,并且不会同时实施其中多种,因此方案的选择互不影响,所以有"子问题独立"。 7、我可以用minMoney[i][j][k]来保存购买第1卷i本,第2卷j本,第3卷k本时所需的最少金钱。 8、共有x * y * z个问题,每个问题面对7种选择,时间为:O( x * y * z * 7) = O( x * y* z )。 9、用函数MinMoney(i,j,k)来表示购买第1卷i本,第2卷j本,第3卷k本时所需的最少金钱,那么有: MinMoney(i,j,k)=min(s1,s2,s3,s4,s5,s6,s7),其中s1,s2,s3,s4,s5,s6,s7分别为对应的7种方案使用的最少金钱: s1 = 60 * 0.95 + MinMoney(i-1,j,k) s2 = 60 * 0.95 + MinMoney(i,j-1,k) s3 = 60 * 0.95 + MinMoney(i,j,k-1) s4 = (60 + 60) * 0.9 + MinMoney(i-1,j-1,k) s5 = (60 + 60) * 0.9 + MinMoney(i-1,j,k-1) s6 = (60 + 60) * 0.9 + MinMoney(i-1,j,k-1) s7 = (60 + 60 + 60) * 0.85 + MinMoney(i-1,j-1,k-1)

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值