背包问题

今天考了一天的试,五科放在一起考,差点累死我

emmm……感觉今天没有上课的话又要跟不上了(本身就是个蒟蒻现在更蒟蒻了)

不过博客还是要接着写(๑•̀ㅂ•́)و✧

 

【01背包问题】

问题:有n个物品和一个容量为m的背包,给出每件物品的体积v和价值w,每件物品只能选一次,求能获得的最大价值

定义:我们用f[i]表示体积为i的背包能获得的最大价值,那么最后的答案就是f[m]

状态转移方程:f[i]=max{f[i],f[i-v]+w}   (i枚举的是背包体积)

理解:每放一个物品,我们都要给这个物品腾出一个v[i]的体积来存放(不然装不下),当前背包的价值加上物品的价值w[i],然后将这个值与原价值比较,较大的那个值就是此时背包能获得的最大价值

知道状态转移方程之后就很好写代码了,另外要注意的是枚举背包体积的时候要从后往前枚举,因为f[i]是从前面的状态转移过来的,先枚举后面的就可以防止一件物品被选取多次,就保证了这是01背包问题

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int M=205;
int f[M];
int main()
{
	int m,n,i,j,v,w;
	scanf("%d%d",&m,&n);
	memset(f,0,sizeof(f));
	for(i=1;i<=n;++i)
	{
		scanf("%d%d",&v,&w);
		for(j=m;j>=v;--j)
		  f[j]=max(f[j],f[j-v]+w);
	}
	printf("%d",f[m]);
	return 0;
}

 

【完全背包问题】

问题:有n个物品和一个容量为m的背包,给出每件物品的体积v[i]和价值w[i],每件物品可以选无数次,求能获得的最大价值

分析:我们观察问题,发现完全背包和01背包唯一的不同就是完全背包中每件物品可以选无数次,我们同样是用f[i]表示体积为i的背包所能获得的最大价值,状态转移方程也和01背包相同,只不过枚举的顺序不同

之前我们说01背包问题中背包体积是从后往前枚举,而完全背包问题不一样,它是从前往后枚举,这样的话,同一个物品就可能会被多次选取(可以结合01背包理解一下),就可以了

代码(代码就把第二层的j循环顺序改了一下):

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int M=205;
int f[M];
int main()
{
	int m,n,i,j,v,w;
	scanf("%d%d",&m,&n);
	memset(f,0,sizeof(f));
	for(i=1;i<=n;++i)
	{
		scanf("%d%d",&v,&w);
		for(j=v;j<=m;++j)
		  f[j]=max(f[j],f[j-v]+w);
	}
	printf("%d",f[m]);
	return 0;
}

 

【多重背包问题】

问题:有n个物品和一个容量为m的背包,给出每件物品的体积v[i]和价值w[i],每件物品可以选s[i]次,求能获得的最大价值

分析:多重背包问题又换了一种花样,物品不是选一次,也不是无数次,而是给定的数s[i]次,那该怎么做呢

我们考虑,若每件物品有s[i]个,那我们可以把它看做是s[i]个不同的物品,只不过每件物品的体积都为v[i],价值都为w[i],每件物品只能选1次,这样的话,这个多重背包问题就可以转换成01背包问题

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int M=6005;
int f[M];
int main()
{
	int m,n,i,j,k,v,w,s;
	scanf("%d%d",&n,&m);
	memset(f,0,sizeof(f));
	for(i=1;i<=n;++i)
	{
		scanf("%d%d%d",&v,&w,&s);
		for(k=1;k<=s;++k)
		  for(j=m;j>=v;--j)
		    f[j]=max(f[j],f[j-v]+w);
	}
	printf("%d",f[m]);
	return 0;
}

 

【混合三种背包问题】

 

问题:这一类题就是将01背包,完全背包,多重背包混合起来,也就是有的物品可以选一次,有的物品可以选无数次,有的物品可以选取的个数有一个上限

其实就是三类背包,在做的时候判断一下就可以了

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=105;
int f[N];
int main()
{
	int n,m,i,j,k,v,w,s;
	scanf("%d%d",&n,&m);
	for(i=1;i<=n;++i)
	{
		scanf("%d%d%d",&v,&w,&s);
		if(s==0)             //0表示的是完全背包,其他的是01背包或者是多重背包 
		{
			for(j=v;j<=m;++j)
			  f[j]=max(f[j],f[j-v]+w);
		}
		else
		{
			for(k=1;k<=s;++k)
			  for(j=m;j>=v;--j)
			    f[j]=max(f[j],f[j-v]+w);
		}
	}
	printf("%d",f[m]);
	return 0;
}

 

【二维费用的背包问题】

 

问题:这一类的问题是指,对于一件物品,具有两种不同的费用,选择这件物品的话必须同时付出这两种代价,其他的条件不变,然后还是求能获得的最大价值

有两种费用了怎么办呢,加一维就可以了

首先物品的两种费用我们用u和v表示,定义f[i][j]表示第一种费用为i,第二种费用为j时能获得的最大价值

动态转移方程:f[i][j]=max{f[i][j],f[i-u][j-v]+w}

理解:还是一样的想法,要想放入这件物品,必须要给先它腾一个位置再放

代码(为了方便找的是01背包的问题):

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=505;
int f[N][N];
int main()
{
	int n,x,y,i,j,k,u,v,w;
	scanf("%d%d",&x,&y);
	scanf("%d",&n);
	for(i=1;i<=n;++i)
	{
		scanf("%d%d%d",&u,&v,&w);
		for(j=x;j>=u;--j)
		  for(k=y;k>=v;--k)
		    f[j][k]=max(f[j][k],f[j-u][k-v]+w);
	}
	printf("%d",f[x][y]);
	return 0;
}

 

【分组的背包问题】

问题:有n件物品和一个容量为m的背包,每件物品的体积为v价值为w,将这些物品划分成若干个组,每个组的物品互相冲突(说人话就是每个组最多只能选一件物品),依然是求能获得的最大价值

那么我们考虑,对于每组物品,你可以选一件,也可以不选(是不是有点像01背包)

正解是这么做的:首先枚举一遍所有的组,再枚举背包容量,然后枚举这一组内的每一个物品,最后转移一下就可以了

理解:后面的两个枚举只会选取最优的那一个物品(或者不选),那么在前面枚举所有的组,就能实现在所有不同的组中选取一个物品(或者不选)使结果最优了

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1005;
int v[N],w[N],f[N],num[N],rec[N][N];
int main()
{
	int n,m,i,j,k,x,s,t=0;
	scanf("%d%d",&m,&n);
	for(i=1;i<=n;++i)
	{
		scanf("%d%d%d",&v[i],&w[i],&s);
		if(!num[s])
		  t++;
		num[s]++;
		rec[s][num[s]]=i;
	}
	for(k=1;k<=t;++k)
	  for(j=m;j>=0;--j)
	    for(i=1;i<=num[k];++i)
	    {
	    	if(j<v[rec[k][i]])
	    	  continue;
	    	x=rec[k][i];
	    	f[j]=max(f[j],f[j-v[x]]+w[x]);
	    }
	printf("%d",f[m]);
	return 0;
}

 

【有依赖的背包问题】

【背包问题的方案总数】

 

【初始化的细节】

如果背包不需要被装满(大多数情况都是这样),那么f数组全部赋为0

如果背包必须要被装满,那么除了f[0]=0外其他的值全部赋为-maxlongint(即很小的数)

至于解释可以戳这里背包问题初始化的细节

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值