球球速刷LC之DP--背包问题 三轮

背包DP

背包问题分为01背包与完全背包
01背包,共有N个物体,每个物体只有一个,装入给定背包中
完全背包,共有N种物体,每个物体数量不限,装入给定背包中
01背包
重点:
1.思路:对每个物体i,在剩余容量j时选择装与不装
2.注意一维情况时的容量倒序遍历

问题定义:给定容量V的背包,和体积分别为{Ci}(i=1...N)的N个物体,每个物体价值为{Wi}(i=1..N).求使得背包价值最大的装法。
状态:对于每个物体i,在背包容量为j时,背包价值为DP(i,j)
选择:
     如果当前背包体积j>=Ci,有装与不装当前物体两种选择
         选择装入当前物体,DP(i,j)=DP(i-1,j-Ci)+Wi (j>=Ci)
         不装入当前物体,   DP(i,j)=DP(i-1,j)
    如果剩余背包体积<Ci,则只能不装当前物体
         DP(i,j)=DP(i-1,j)

故转移方程:

for i=1....N
   for j=0...V
     if(j>=Ci) DP(i,j)=max{DP(i-1,j),DP(i-1,j-Ci)+Wi};
     if(j<Ci) DP(i,j)=DP(i-1,j)

优化为1维数组
由原始递推式,DP(i,j)仅仅依赖于DP(i-1,XX)状态,考虑优化为1维数组。由于计算DP(i,j)时需要保证DP(i-1,j-Ci)还没有更新,因此容量需要倒序遍历

for i=1....N
   for j=V...0 //此处注意:由于在计算DP(j)时需要DP(j-Ci)依然保持在i-1的状态,所以V需要倒序遍历
      if(j>=Ci)DP(j)=max{DP(j),DP(j-Ci)}

01背包其他问法:根据问法更改转移方程的函数
<1>是否能恰好装满
二维状态定义:DP(i,j)代表第i个物体容量为j时是否恰好装满

二维状态转移方程:
DP(i,j)=DP(i-1,j)||DP(i-1,j-Ci) (j>=Ci)
DP(i,j)=DP(i-1,j)               (j<Ci)
一维状态转移方程:
DP(j)=DP(j)||DP(j-Ci) (j>=Ci)(j=V....0) //注意01背包问题1维状态需要倒序遍历
初始化
DP[0]=TRUE    //容量为0时一个物体都不选恰好能装满,故DP[0]=1;
DP[1....V]=FALSE

<2>恰好装满的方案总数
二维状态定义:DP(i,j)代表第i个物体容量为j时恰好装满的总方案数

二维状态转移方程:
DP(i,j)=DP(i-1,j)+DP(i-1,j-Ci) (j>=Ci)
DP(i,j)=DP(i-1,j)              (j<Ci)
一维数组状态:
DP(j)=DP(j)+DP(j-Ci) (j>=Ci)(j=V....0) //注意01背包问题1维状态需要倒序遍历
初始化
DP[0]=1 //容量为0时至少存在 一个物体都不选这个方案,故初始化为1
DP[1....V]=0

<3>恰好装满所需最少物体数
二维状态定义:DP(i,j)代表第i个物体容量为j时恰好装满所需最少物体数,物体数方案不存在返回-1

二维状态转移方程:
DP(i,j)=min{DP(i-1,j),DP(i-1,j-Ci)+1}(j>=Ci)
DP(i,j)=DP(i-1,j) (j<Ci)
一维数组状态:
DP(j)=min{DP(j),DP(j-Ci)} (j=V....0) //注意01背包问题1维状态需要倒序遍历
初始化
DP[0]=0 //容量为0时一个都不选,所需物体数最少
DP[1....V]=V+1 //所需物体数一定<=V,V+1代表初始化为未找到有效方案状态

完全背包

问题定义:给定n种物体,每种物体数量不限,每种物体的体积为Ci,价值为Wi。
向容量为V的背包中装入这n种物体,求背包能装入的最大价值。与01背包的最大不同是,此时每种物体的数量不限。
状态定义:在对第i种物体进行选择,背包体积为j时。DP(i,j)表示此时背包最大价值
选择:
当前物体体积小于背包容积j,则可选择装入或不装入
装入:DP(i,j)=DP(i,j-Ci)+Wi  //此处与01背包最大不同是,由于选择装入物体i后,可以选择继续装入物体i。故此处为DP(i,j-Ci)而不是DP(i-1,j-Ci)
不装入:DP(i,j)=DP(i-1,j)
当物体i体积大于j,只能选择不装入
DP(i,j)=DP(i-1,j)

状态转移方程:

DP(i,j)=max{DP(i-1,j),DP(i,j-Ci)+Wi} (j>=Ci)
DP(i,j)=DP(i-1,j) (j<Ci)

完全背包一维优化
由状态转移方程,DP(i,j)最多依赖于DP(i-1,j),且计算DP(i,j)时需要DP(i,j-Ci)已经计算。
故一维状态方程

for i=1.....N
   for j=Ci....V //为保证j>=Ci ,j从Ci开始遍历
      DP(j)=max{DP(j),DP(j-Ci)+Wi}   //注意此时对容量遍历是正序遍历,与01背包正好相反

其他问法:
完全背包也包括恰好装满方案数、装满所需最少数量、能否恰好装满等问法。

leetcode 练习

416分割等和子集333(0-1背包,是否恰好能装下)
    bool canPartition(vector<int>& nums) {
   
        if(nums.empty()){
   
            return false;
        }        
        unsigned int sum=0;
        for(auto i:nums) sum+=i;
          
        if(sum%2!=0) return false;
        //问题转化为在nums中是否存在n个数之和恰好为sum/2
        //转化为01背包问题,求是否能恰好装满
        unsigned int target=sum/2;
        vector<vector<bool>>dp(nums.size()+1,vector<bool>(target+1,false));
        dp[0][0]=true; //dp[0][0]代表一个物体都不选,恰好能装满容量为0背包
        //转移方程
        //DP(i,j)=DP(i-1,j)||DP(i-1,j-Ci) j>=Ci
        //DP(i,j)=DP(i-1,j) j<Ci
        for(int i=1;i<=nums.size();++i){
   
            for(int j=0;j<=target;++j){
   
                //当前选择第i个物体,体积为nums[i-1],背包容量为j.
                int Ci=nums[i-1];
                if(j>=Ci){
   
                    dp[i][j]=dp[i-1][j]||dp[i-1][j-Ci]];
                }else{
   
                    dp[i][j]=dp[i-1][j];
                }
            }
        }
        return dp[nums.size()][target];
    }

优化为1维状态

    bool canPartition(vector<int>& nums) {
   
        if(nums.empty()) return false;
        int sum=0;
        for(auto i:nums)sum+=i;
        
        if(sum%2==1) return false;
        int pack=sum/2;//背包容量为和的一半
        
        //01背包问题:状态方程
        
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值