01背包,完全背包,多重背包模板

本文用的是DP,其中f[v]表示存储的是前i个物体放到容量v时的最大价值

一,0/1背包问题

详讲可以看
(https://blog.csdn.net/liusuangeng/article/details/38374405)
我是这看懂的

例题引入:
0/1背包
Description

给定n个物品和一个背包。物品i的重量是wi,其价值为vi,背包的容量为C。问:应该如何选择装入背包的物品,使得装入背包中物品的总价值最大?

Input

输入的第一行为测试样例的个数T,接下来有T个测试样例。

每个测试样例的第一行是物品个数n(1 ≤ n ≤ 3500)和背包容量C(C ≤ 13000),接下来n行,每行两个正整数 wi和 vi( wi ≤ 1000, vi ≤ 1000 ),分别表示第i件物品的重量 wi及其价值 vi。

Output
对应每个测试样例输出一行,只有一个整数,表示总价值的最大值。

Sample Input

2
1 2
1 1
2 3
2 3
2 4

Sample Output

1
4

一维滚动数组01背包模板:

#include<bits/stdc++.h>
using namespace std;
/*有n件物品和一个容积为C的背包。1 ≤ N ≤ 1000 ,   1 ≤ m ≤ 1000
第i件物品的重量w[i],价值是v[i]。*/
int main(void)
{
	int n,C;
	int w[1001]={0},v[1001]={0};
	int f[13001];//n,m太大了用二维超空间了,应该用滚动数组 f[容量+1] 
	int i,j,t;
	scanf("%d",&t);
	while(t--)
	{
		memset(f,0,sizeof(f));//本题要求是在“不超过”背包容量的情况下,最多能获得多少价值或收益 
		//则数组f[]初始化为0;
		//如果是 “恰好装满”背包的情况下,最多能获得多少价值或收益,则数组全初始话为负无穷,但是f[0]=0;
		scanf("%d %d",&n,&C);//n个物体,m的容量
		for(i=1;i<=n;i++)
		scanf("%d %d",&w[i],&v[i]);
		//01背包核心部分: 
		for(i=1;i<=n;i++)
		for(j=C;j>=w[i];j--)
		f[j]=max(f[j],f[j-w[i]]+v[i]);//max( 不挑选,挑选 );
		printf("%d\n",f[C]);
	}
	return 0;
}

以下是用二维数组的01背包模板:

#include<bits/stdc++.h>
using namespace std;
/*有n件物品和一个容积为C的背包。1 ≤ N ≤ 1000 ,   1 ≤ m ≤ 1000
第i件物品的重量w[i],价值是v[i]。*/
int inf=-999999999;
int f[1001][1001];//数组太大了,必须设在这里 
int main(void)
{
	int n,C;
	int w[1001]={0},v[1001]={0};
//	int f[1001][1001];//n,m太大了用二维超空间了,应该用滚动数组 f[容量+1] 
	int i,j;
	scanf("%d %d",&n,&C);//n个物体,m的容量
	/*for(i=0;i<=n;i++)
	for(j=0;j<=C;j++)
	f[i][j]=inf;
	for(i=0;i<=n;i++)
	f[i][0]=0;
	如果要求是在“恰好装满”背包容量的情况下,最多能获得多少价值或收益 
	则数组全初始话为负无穷,但是f[i][0]=0;*/ 
	//但本题是 “不超过”背包的情况下,最多能获得多少价值或收益,则数组f[i][j]初始化为0;
	memset(f,0,sizeof(f));
	for(i=1;i<=n;i++)
	scanf("%d %d",&w[i],&v[i]);
	//01背包核心部分: 
	for(i=1;i<=n;i++)
	for(j=0;j<=C;j++)
	if(j<w[i])
	f[i][j]=f[i-1][j];
	else
	f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+v[i]);//max( 不挑选,挑选 );
	printf("%d\n",f[n][C]);
	return 0;
}

分析:
时间复杂度为O(n*C),如果体积很大但是价值很小,很容易超内存就是另外叫超大背包的问题了。

二,完全背包

解析(https://www.cnblogs.com/Kalix/p/7622102.html)

我觉得讲的不错,重点我标出来
1.“看完这个问题,你也许会觉得这个不就是01背包的升级版吗,其实就是这样,完全背包问题与01背包问题的区别在于完全背包每一件物品的数量都有无限个,而01背包每件物品数量只有1个
所以说与它相关的策略已经不是只有取和不取这两种策略了,而是有取0件、取1件、取2件……等等很多种策略”
2.“即:将一种物品拆成多件物品。
我们现在dp每一个物品,dp出该种物品不同剩余容量下的最优解,他是以每1个为单位的。考虑是否在当前所有物品总数中添加一件新的该物品”
+在这里插入图片描述
例题引入:

超市搞活动(完全背包)

Description

某超市举行活动,凡参加活动的市民,可以领到一个容量为C的箱子。超市里面的商品任意挑选,每种商品可拿的个数也无限制,只要能装进这个箱子(不超出箱子的容量),就可以免费拿走。

Input

多测试用例。

每个测试用例第一行是两个正整数 C 和 N ,( 0 < C ≤ 10,000, 0 < N < 10,000 ),C 表示箱子的容量,N 表示超市里面商品的总数量。

接下来 N 行,每行两个正整数 Pi 和 Qi ( 0 < Pi < 1000 , Qi > 0 , 1 ≤ i ≤ N ) ,表示第i件商品的价值和体积。

Output

每个测试用例输出一行:你能够免费拿走的商品的最大价值总和。

Sample Input

4 2
7 3
4 2

Sample Output

8

只要把“不超过”背包容量”01背包模板核心for循环改成顺序(即j从0~C)且修改状态方程即可

完全背包和01价值背包问题的区别在于每一件物品的数量都有无限个,而01背包每件物品数量只有一个。
在递推公式时,需要加以改变:dp[i][j] = max(dp[i - 1][j], dp[i][j - weight[i]] + value[i]); 注意这里需要考虑放入一个物品i时还可能继续放入i,所以不能是dp[i][j] = max(dp[i - 1][j], dp[i-1][j - weight[i]] + value[i]);

现在呈上完全背包的代码

#include<bits/stdc++.h>
using namespace std;
int main(void)
{
	int n,C;
	int f[10001];
	int i,j,w[10001],v[10001];
	while(scanf("%d %d",&C,&n)!=EOF)
	{
		for(i=1;i<=n;i++)
		scanf("%d %d",&v[i],&w[i]);
		memset(f,0,sizeof(f));
		for(i=1;i<=n;i++)
		for(j=w[i];j<=C;j++)
		f[j]=max( f[j],f[j-w[i]]+v[i]);
		printf("%d\n",f[C]);
	}
	return 0;
}

三,多重背包

例题引入:

多重背包

Description

给定N种物品和一个容量为C的背包,第i种物品最多有 Mi 件可用,每件的重量是Wi,价值是Vi。问:将哪些物品装入背包可使这些物品的重量总和不超过背包容量,且价值总和最大。

Input

输入的第一行为测试样例的个数T,接下来有T个测试样例。

每个测试样例的第一行是物品种数N(1 ≤ N ≤ 100)和背包容量C(C ≤ 10000)。

接下来N行,每行三个正整数,Wi ,Vi 和 Mi ( Wi ≤ 10000, Vi ≤ 10000, Mi ≤ 10000 ),分别表示第i种物品的重量 Wi ,价值 Vi ,及个数 Mi 。

Output

对应每个测试样例输出一行,只有一个整数,表示装入背包的物品总价值的最大值。

Sample Input

1
2 8
4 100 2
2 100 4

Sample Output

400

多重背包和01背包、完全背包的区别:多重背包中每种物品的数量是给定的,可能不是一个,绝对不是无限个。
方法一:转化为01背包。
方法二:计算考虑k种物品承重限制为N时最大价值f[k][N]时,递推公式考虑两种情况,要么第 i 种物品一件也不放,就是f[i-1][j], 要么第 i 种物品放 k 件,其中 1 <= k <= (N/weight[i]),考虑这一共 k+1 种情况取其中的最大价值即为f[i][j]的值,即f[i][j] = max{f[i-1][j], (f[i-1][j-k*weight[i]]+kvalue[i])}。 这里为什么不能像完全背包一样直接考虑f[i][j-weight[i]]+value[i]呢?因为这样不容易判断第 i 种物品的个数是否超过限制数量 num[i]。
很遗憾当数据过大时,以上两种方法均超时。我们还有两种方法
1.二进制的优化
“二进制的优化
这是一个多重背包的模板,也是十分好用的一种模板,因为这个比直接拆除01 背包来做
要省些时间。这是为啥呢,首先先由我讲一下为什么能换成01 背包吧。
举个例子。假如给了我们 价值为 2,但是数量却是10 的物品,我们应该把10给拆开,要知道二进制可是能够表示任何数的,所以10 就是可以有1,2, 4,8之内的数把它组成,一开始我们选上 1了,然后让10-1=9,再选上2,9-2=7,在选上 4,7-4=3,
而这时的3<8了,所以我们就是可以得出 10由 1,2,4,3,来组成,就是这个数量为1,2,3,4的物品了,那么他们的价值是什么呢,是2,4,6,8,也就说给我们的价值为2,数量是10的这批货物,已经转化成了价值分别是2,4,6,8元的货物了,每种只有一件哎!!!!这就是二进制优化的思想。”
附上我提交的代码,我用方法1和2都超时了
二进制优化AC了

#include<bits/stdc++.h>
using namespace std;
struct node{
 int w;
 int v;
};
int main(void)
{
 int n,C;
 int f[10001];
 struct node wuping[20001];//一定小于2n;
 int k,t,i,j,count,w,v,m;
 scanf("%d",&t);
 while(t--)
 {
  count=0;
  memset(f,0,sizeof(f));
  scanf("%d %d",&n,&C);
  for(i=1;i<=n;i++){
  scanf("%d %d %d",&w,&v,&m);//重量 价值 数量 
  k=1;
  while(m-k>0)
  {
   m-=k; 
   wuping[++count].w =k*w;//重量翻倍 
   wuping[count].v =k*v;//价值翻倍 
   k*=2;//指数增长 
  }
  wuping[++count].w =m*w;//剩下不足指数的数m 
  wuping[count].v =m*v;//不足指数的 
  }
  //成功转换为01背包
  for(i=1;i<=count;i++)//总数变为count
  for(j=C;j>=wuping[i].w ;j--)//容量 
  f[j]=max( f[j],f[j-wuping[i].w ]+wuping[i].v ); 
  printf("%d\n",f[C]);//f[容量]
 }
 return 0;
}

2.单调队列优化
(https://www.cnblogs.com/shuaihui520/p/9043143.html)
…这里提到还提到了另外一种优化:“那为什么会有完全背包和01 背包的不同使用加判断呢?原因也很简单啊,当数据很大w[i]*num,大于背包的容纳量时,我们就是在这个物品中取上几件就是了,这就是完全背包,反而小于容纳量(w[i]*num<v)的就是转化为01背包来处理就是了,可以大量的省时间。”
就是打起来有点麻烦(比二进制优化好点,但是还是没单调队列优化省时)~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值