完全背包问题+01背包问题+分组背包+多重背包 总结

本文转载自:https://blog.csdn.net/weixin_43849505/article/details/89423390

背包问题都涉及到动态规划,利用dp进行更加优化的计算。

一、01背包

最基本的是01背包问题,题目一般类似:“在一定数目物品内,挑选总重量不超过一定数目的物品,其中每个物品只能选一次,求背包内物品价值的最大值或者最小值”,从名字就可以看出,要么选0个,要么选1个。

如果按照暴力的方法,时间复杂度会爆表,这里采用的是记忆化搜索的方式,加以DP。

首先,选一个二维数组dp,这个数组的含义是从前i个物品中选出总重量不超过j的物品时总价值的最大值,也就是说,对于数组中的任意一个元素dp[i][j],其含义为在前i个物品中选出总重量不超过j的物品的最大值。

定义好了数组,接下来就是找状态转移方程。最容易找到的就是最初的状态,即dp[0][j]=0,在一个物品也不选的时候,不论总质量是多少,总价值肯定是0。接下来的状态都是在这个基础状态上进行扩展。既然是放物品,那么肯定是物品一个一个遍历,如果能放不下,其最大价值肯定还等于上一个状态,也就是说当j<w[i]时,dp[i+1][j]=dp[i][j],如果能放下,就需要比较是放下这个物品价值大还是不放价值大,当选择放的时候,相当于在前i个物品中,空间为当前空间减去w[i+1]的情况再放这个物品,比较这个情况下价值的大小,即当j>=w[i]时,dp[i+1][j]=max(dp[i][j],dp[i][j-w[i]]+v[i]),这样所有的状态我们就找到了,即状态转移方程为:
dp[0][j]=0;
dp[i+1][j]=dp[i][j] i<w[i];
dp[i+1][j]=max(dp[i][j],dp[i][j-w[i]]+v[i]) j>=w[i]

根据这个就可以写出01背包最大值问题的模板了

for(int i=0;i<N;i++)
{
	for(int j=0;j<=wei;j++)
	{
		if(j<w[i])
			dp[i+1][j]=dp[i][j];
		else
			dp[i+1][j]=max(dp[i][j],dp[i][j-w[i]]+v[i]);
	}
}
cout<<dp[N][wei]<<endl;

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

在这个的基础上可以进行空间复杂度上的优化,不难发现每次对下一行的数据的更新都是取决于上一行的数据,所以并不一定需要开一个二维数组,一维数组就可以解决问题,我们关心的是最后一次外循环的值,中间过程我们并不需要知道,所以可以进行空间上的优化。

二维数组中我们进行的操作实际上就是在每个物品的基础上,遍历所有可能的背包空间,如果放得下去就比较放与不放对价值的影响,转换为一维的时候,其实比较的就是在上一次操作的情况下,哪些还可以放得下这个物品,放得下的就进行比较。所以也就是要对w[i]到wei的范围进行更新,转换为代码就是下面这个形式,转换后dp数组的含义就变成了容量不超过j时的最大价值了,省略了前几个物品这一项。

for(int i=0;i<N;i++)
{
	for(int j=wei;j>=w[i];j--)
	{
		dp[j]=max(dp[j],dp[j-w[i]]+pri[i]);
	}
}
cout<<dp[wei]<<endl;

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

这里还需要注意,01背包的一维数组表示,更新的时候是逆序的,从代码不难看出,对一个值的更新是基于这个值左边的值进行更新的,如果是顺序更新,那么在更新时一定会先更新左边的值,从而使得后更新的值有可能基础被更新过了,而逆序更新就可以避免这个问题。也可以这么说,因为01背包里面每个物品只能放一次,如果顺序更新,那么假设状态1已经放了一个物品了,那么基于状态1的状态2如果需要更新,那么不就变成了放两个物品了么,这不就出错了,所以应该需要逆序更新。

这里举例子都是举的求最大值,这时需要把数组初始化为0,之后每一次都选取最大值即可。而当要求的是最小值时,则应该把数组初始化为很大的数,每次选取最小值。dp[N][wei]就是要求的最值。

二、完全背包

完全背包问题是在01背包问题上进行的延伸,01背包每次物品的数目只能是0或者1,而完全背包问题就脱离了这个限制,题目一般类似:“在一定数目物品内,挑选总重量不超过一定数目的物品,其中每个物品可以选多次,求背包内物品价值的最大值或者最小值”。这里就需要对上面的模板进行一定的修改了。

其实最笨的办法是在01背包的基础上再增加一层循环,用于记录增加的件数

for(int i=0;i<N;i++)
	for(int j=0;j<=wei;j++)
		for(int k=1;k*wei[i]<=j;k++)
			dp[i+1][j]=max(dp[i][j],dp[i][j-k*wei[i]]+k*val[i]);
cout<<dp[N][wei]<<endl;

   
   
  • 1
  • 2
  • 3
  • 4
  • 5

这种方法比较好懂,但是时间复杂度太大,需要进行优化,这里直接在01背包一维的基础上进行一个优化,其实这两种背包问题都是相通的,区别就在于01不可以放多个而完全背包可以放多个,在前面讲一维化的地方说过,之所以逆序更新是防止放两个的情况出现,那么这里完全背包就正好利用了这一点,把01背包的逆序更新换成顺序更新,就可以解决放几个的问题了,把所有还可以放的状态都更新一遍,从左向右进行更新,如果前面的状态就已经放得下一个了,那么基于这个状态的另一个状态还可以再放一个,所以就基于上个状态继续更新,因而是正序更新。具体表示为,当背包空间为j时,最大价值等于当前值和加上一个当前物品增加价值之后中更大的值。代码如下

for(int i=0;i<N;i++)
{
	for(int j=w[i];j<=wei;j++)
	{
		dp[j]=max(dp[j],dp[j-w[i]]+pri[i]);
	}
}
cout<<dp[wei]<<endl;

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

所以在记忆这两种背包问题的时候,只需要理解好更新顺序的问题,模板都是一样的,只需要根据顺序进行修改就好了。

三、分组背包

分组背包问题是在01背包的基础上又进行了延伸,01背包问题中每个物品要么选要么不选,在分组背包中,将一系列物品分成几组,一次在一个小组中选择一个或者不选,其实01背包是一种特殊情况下的分组背包,即每个组只有一个物品时的分组背包,我们依然可以沿用01背包的解题思路,只不过进行一下加工,这里直接取一维情况的dp数组,其实我们可以这样认为,每个组既然只能选一个,我们可以先限定一个组别,之后在这个组别中,在总重量允许的条件下,看当前重量下是不是放得下这个组中的物品,放得下就选取价值的更大值。由于每次选择的都是最大值而且是在这一个组中进行的操作,所以保证了一个组中只选择了一个物品。由于分组背包是01背包延伸来的,所以依然是用逆序遍历来实现一维数组的dp。

模板类似下面的代码

for(int i=1;i<=N;i++)//第几组
			for(int j=M;j>=1;j--)//允许的重量 
				for(int k=1;k<=这组的物品数;k++)//每组选物品 
					if(物品的重量<=j)
						dp[j]=max(dp[j],dp[j-物品的重量]+val[i][k]);

   
   
  • 1
  • 2
  • 3
  • 4
  • 5

四、多重背包

多重背包问题的思路跟完全背包的思路非常类似,只是每种物品的取值是有限制的,因为每件物品的数量是有限制的。

多重背包一般采取转化为01背包的方法,将每个物品按照2的幂次分成几个物品,这样就变成了01背包。把第i种物品换成n[i]件01背包中的物品。考虑二进制的思想,考虑把第i种物品换成若干件物品,使得原问题中第i种物品可取的每种策略——取0…n[i]件——均能等价于取若干件代换以后的物品。另外,取超过n[i]件的策略必不能出现。分成的这几件物品的系数和为n[i],表明不可能取多于n[i]件的第i种物品。这里一般都会用到二进制优化来减少时间复杂度,不然的话三层循环暴力确实有些吃不消。

模板如下

for(int j=1;j<=num[i];j*=2)//二进制优化 
{
	for(int k=n;k>=j*i;k--)//转换为01背包,所以需要逆序更新 
	{
		if(dp[k-i*j]==1)
			dp[k]=1;
	}
		num[i]-=j;//剩余数量进行更新 
}
if(num[i]*i!=0)//对于不能进行二进制更新的部分直接当做一个物品处理 
{
	for(int k=n;k>=num[i]*i;k--)//对于剩下的部分应该也遍历一遍 
	{
		if(dp[k-num[i]*i])
			dp[k]=1;
	}
}

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

五、总结

总的来说,这四类背包问题的关系大致如下:
01背包:每个物品只有一个,要么选要么不选
完全背包:在01背包基础上解除了只有一个的限制,每个物品随便选
分组背包:嵌套的01背包,每组要么选一个要么不选,每组里面的物品只能选一个,也可以理解为最大数量为1的要么选要么不选
多重背包:在完全背包上加了数量的限制,依然是随便选,但不能超过限制。

结题思路
01背包:逆序更新
完全背包:顺序更新
这两个是基础,理解好原理就不难区分
分组背包:01背包的基础上,再套一层循环来检验每一组的物品
多重背包:二进制优化后转化为01背包

                                </div>
            <link href="https://csdnimg.cn/release/phoenix/mdeditor/markdown_views-b6c3c6d139.css" rel="stylesheet">
                                            <div class="more-toolbox">
            <div class="left-toolbox">
                <ul class="toolbox-list">
                    
                    <li class="tool-item tool-active is-like "><a href="javascript:;"><svg class="icon" aria-hidden="true">
                        <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#csdnc-thumbsup"></use>
                    </svg><span class="name">点赞</span>
                    <span class="count">1</span>
                    </a></li>
                    <li class="tool-item tool-active is-collection "><a href="javascript:;" data-report-click="{&quot;mod&quot;:&quot;popu_824&quot;}"><svg class="icon" aria-hidden="true">
                        <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#icon-csdnc-Collection-G"></use>
                    </svg><span class="name">收藏</span></a></li>
                    <li class="tool-item tool-active is-share"><a href="javascript:;"><svg class="icon" aria-hidden="true">
                        <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#icon-csdnc-fenxiang"></use>
                    </svg>分享</a></li>
                    <!--打赏开始-->
                                            <!--打赏结束-->
                                            <li class="tool-item tool-more">
                        <a>
                        <svg t="1575545411852" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5717" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M179.176 499.222m-113.245 0a113.245 113.245 0 1 0 226.49 0 113.245 113.245 0 1 0-226.49 0Z" p-id="5718"></path><path d="M509.684 499.222m-113.245 0a113.245 113.245 0 1 0 226.49 0 113.245 113.245 0 1 0-226.49 0Z" p-id="5719"></path><path d="M846.175 499.222m-113.245 0a113.245 113.245 0 1 0 226.49 0 113.245 113.245 0 1 0-226.49 0Z" p-id="5720"></path></svg>
                        </a>
                        <ul class="more-box">
                            <li class="item"><a class="article-report">文章举报</a></li>
                        </ul>
                    </li>
                                        </ul>
            </div>
                        </div>
        <div class="person-messagebox">
            <div class="left-message"><a href="https://blog.csdn.net/weixin_43849505">
                <img src="https://profile.csdnimg.cn/9/F/B/3_weixin_43849505" class="avatar_pic" username="weixin_43849505">
                                        <img src="https://g.csdnimg.cn/static/user-reg-year/1x/1.png" class="user-years">
                                </a></div>
            <div class="middle-message">
                                    <div class="title"><span class="tit"><a href="https://blog.csdn.net/weixin_43849505" data-report-click="{&quot;mod&quot;:&quot;popu_379&quot;}" target="_blank">不娶为何要撩</a></span>
                                        </div>
                <div class="text"><span>发布了197 篇原创文章</span> · <span>获赞 14</span> · <span>访问量 1万+</span></div>
            </div>
                            <div class="right-message">
                                        <a href="https://im.csdn.net/im/main.html?userName=weixin_43849505" target="_blank" class="btn btn-sm btn-red-hollow bt-button personal-letter">私信
                    </a>
                                                        <a class="btn btn-sm  bt-button personal-watch" data-report-click="{&quot;mod&quot;:&quot;popu_379&quot;}">关注</a>
                                </div>
                        </div>
                </div>
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值