背包问题2—完全背包和多重背包

完全背包问题描述:
有n种物品和一个容量为m的背包,每种物品都有无限件可用。第i种物品的费用是w[i],价值是v[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
看过01背包的对于背包应该会有一定的了解,若果没看的话可以看我上一篇文章:
https://blog.csdn.net/weixin_44606952/article/details/98944585
看完后就会明显直到两种问题的不同就是在于可以无限的取用,想取多少个就有多少只要在最大容量的允许范围内并且还要在有限的空间获得最大的价值;其实和01背包基本一样只需要改一下第二重循环就行,这也就是我在01背包里面讨论到的会重复问题
核心代码:

for(i=1; i<=n; i++)
    	    for(j=w[i]; j<=m; j++)
		      dp[j]=max(dp[j],dp[j-w[i]]+v[i]);

dp[j]还是表示容量为j时所能够获得的最大价值;
因为当遇到i时需要的容量为w[i],所以最少也要有w[i]个空间来放i,然后比较价值关系判断是否要加如第i个物品,然后后面依次尝试在不停加入i且容量允许的的情况下是否会得到更优解(变化的并不是只有当j为最大容量时,当j不是最大即没有占完所有空间的情况下也会被优化,因为在后面的判断最大容量时可能会用到例如dp[j-w[i]]要是等于dp[5]的话就要用到dp[5]了所以其他数值应当也被优化),
在这我还想 描述的就是最大装入i的值是与i的容量w[i]有关,最大就是m/w[i];
例如第i个物品所需要的空间为5,最大容纳量为10,在dp[5]的时候可以选一次,在dp[10]的时候还可以选一次(为方便描述考虑选择第i的情况下dp[10]=dp[5]+v[i]这样可以明显看出来多选了
下面我给出做题的样例模板

#include<stdio.h>
#include<string.h>
#define max(a,b) a>b?a:b
int n,m;
int dp[2000],w[2000],v[2000];
int main()
{
   	int i,j,k,t;
   	scanf("%d", &t);
   	while(t--)
      {
      	memset(dp,0,sizeof(dp));
   	scanf("%d %d", &n,&m);
   	for(i=1; i<=n; i++)
   	   scanf("%d", &v[i]);
   	   for(i=1; i<=n; i++)
   	    scanf("%d", &w[i]);
   	for(i=1; i<=n; i++)
   	    for(j=w[i]; j<=m; j++)
   	      dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
   	     printf("%d\n", dp[m]);
   }
   return 0;
}

下面我简单说描述多重背包,它与01背包的不同就是他对于每一件物品的数量是有一定的数量的其他情况也还是一点也不变,
先看核心代码吧:

	for(i=1; i<=n; i++)
		   for(k=1; k<=s[i]; k++)
    	     for(j=m; j>=w[i]; j--)
		      dp[j]=max(dp[j],dp[j-w[i]]+v[i]);

其实吧我对于它的理解就是在01背包的基础上加了一点即对于每一个物品尝试它的个数的每一种情况及若有4个将其 放入dp[j]中查看价值关系就行,他也是可以多次放入问题只不过他多次放入是用另外一个循环来控制的
下面 同样给出模板(实际上与上面不同的也就是一行代码):

#include<stdio.h>
#include<string.h>
#define max(a,b) a>b?a:b
int n,m;
int dp[2000],w[2000],v[2000];
int main()
{
    	int i,j,k,t;
    	scanf("%d", &t);
		while(t--)
	   {
	   	memset(dp,0,sizeof(dp));
		scanf("%d %d", &n,&m);
		for(i=1; i<=n; i++)
		   scanf("%d", &v[i]);
		   for(i=1; i<=n; i++)
		    scanf("%d", &w[i]);
		for(i=1; i<=n; i++)
		   for(k=1; k<=s[i]; k++)
    	     for(j=m; j>=w[i]; j--)
		      dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
		     printf("%d\n", dp[m]);
    }
    return 0;
}

二进制转换方法将第 i 种物品分成若干件 01 背包中的物品,其中每件物品有一个系数。这件物品的费用和价值均是原来的费用和价值乘以这个系数。令这些系数分别为
1, 2, 2^2 . . . …
2^k 1, Mi - 2k + 1(最后一个系数,且 k 是满足 Mi 2k + 1 > 0 的最大整数。例如,如果 Mi (件物品个数)为 13,则相应的 k = 3,这种最多取 13 件的物品应被分成系数分别为 1, 2, 4, 6 的四件物品。
这个代码相对与上面来说虽然有点难但是它的时间复杂复杂度小(用空间换时间),所以还是有学习的必要
下面是代码

#include<stdio.h>
#include<string.h>
#define max(a,b) a>b?a:b
int dp[10000],v[10000],w[10000],num[10000];
int n,m;
void zeroone(int v,int w,int n)//01背包
{
	for(int i=n; i>=w; i--)
	  dp[i]=max(dp[i],dp[i-w]+v);
}
void complete(int v,int w,int n)//完全背包
{
	for(int i=w; i<=n; i++)
	  dp[i]=max(dp[i],dp[i-w]+v);
}
int multi(int n,int m,int v[],int w[],int num[])//多重背包
{
	int i,k;
	for(i=1; i<=n; i++)//枚举每一件物品
	{
		if(num[i]*w[i]>m)//如果第i件背包的数量乘以个数大于
		                  //总容量就视为完全背包可以无限取用
		 complete(v[i],w[i],m);
		 else
		 {
		 	k=1;//说通俗点就是把这一件背包打包为不同重量的包裹
		 	while(k<num[i])
		 	{
		 		zeroone(k*v[i],k*w[i],m);
		 		num[i]-=k;//减去已尝试的背包
		 		k=k*2;//在此打包
			 }
			 zeroone(num[i]*v[i],num[i]*w[i],m);//有最后一组即不够满足2^k-1次方的那一件包裹
		 }
	}
	return dp[m];
}
int main()
{
	int i,j,t;
	scanf("%d", &t);
	while(t--)
	{
		memset(dp,0,sizeof(dp));
		scanf("%d %d", &m,&n);
		for(i=1; i<=n; i++)
		  scanf("%d %d %d", &w[i],&v[i],&num[i]);
		  printf("%d\n", multi(n,m,v,w,num));
    }
	return 0;
}

混合背包:
混合背包实际上也就是把三种背包问题混合在一起,我在做的时候就是把三种背包分别拆开来写,写成三个函数的话就可以解决,再结合相对应的题目来解决对于一些相对来说较简单的题目还是很容易解决的;
下面是我整理的混合背包的文章链接:
https://blog.csdn.net/weixin_44606952/article/details/99076772

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

皮皮皮皮皮皮皮卡乒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值