多种类型的背包问题

1、0/1背包

将物品划分为两个集合:①从1~i中选不包含i且体积小于等于V的,即f(i-1,j)
②从1~i中选包含i且体积小于等于V的,即f(i-1,j-v[i])+w[i];f(i,j)表示从1~i中选且体积不超过j的物品的最大价值。

为什么需要逆序?

由转移方程f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]]),如果从小到大枚举的话,j递增,j-v[i]一定小于j,当更新本层时j-v[i]就会取到本层前面f[i][j],而不是上一层的。

#include<iostream>
using namespace std;
const int N=1e5+10;
const int M=1e3;
int v[N],w[N],dp[N][M];
int main()
{
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++)
		cin>>v[i]>>w[i];
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			dp[i][j]=dp[i-1][j];
			if(v[i]<=j)
				dp[i][j]=max(dp[i][j],dp[i-1][j-v[i]]+w[i]);
		}
	}
	//优化,可发现每次在更新i时我们只需要用到前面i-1的值,所以用一维数组来存储,即滚动数组的形式
	for(int i=1;i<=n;i++)
		for(int j=m;j>=v[i];j--)
		{	//逆序过来,否则因为j-v[i]<=j的,所以当dp[j]=dp[j-v[i]]时,dp[j-v[i]]可能用到的是第i层的dp[i][j-v[i]],而实际要的是dp[i-1][j-v[i]]
			dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
		}

	cout<<dp[n][m]<<endl;//我们要的答案是从n件物品中选体积不大于m的最大价值,所以答案是dp[n][m]。
}

2、完全背包(每件物品可以取无限多个)

状态表示:f[i][j]从1~i中每件物品选无限多个且体积不超过V的最大价值。
状态划分:对每件物品可以取0,1,2...k,状态方程f[i][j]=f[i-1][j-k*v[i]]+k*w[i]。

#include<iostream>
using namespace std;
const int N=1010;
int v[N],w[N],f[N];
int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%d%d",&v[i],&w[i]);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			for(int k=0;k*v[i]<=j;k++)
				f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
		}
	}
	//优化①,f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i],f[i-1][j-2v[i]]+2w[i]]...,f[i-1][j-kv[i]]+kw[i])
	//      f[i][j-v[i]]=max(     f[i-1][j-v[i]],f[i-1][j-2v[i]]+w[i],...,f[i-1][j-kv[i]]+(k-1)w[i])
	//f[i][j]=max(f[i-1][j],f[i][j-v[i]]+w[i])
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			f[i][j]=f[i-1][j];
			if(j>=v[i])
				f[i][j]=max(f[i][j],f[i][j-v[i]]+w[i]);
		}
	}
    //优化②,一维数组存储
	for(int i=1;i<=n;i++)
	{
		for(int j=v[i];j<=m;j++)//不需要逆序,因为用到的值是在同一层的
		{
			f[j]=max(f[j],f[j-v[i]]+w[i]);
		}
	}
	printf("%d\n",f[m]);
	return 0;
}

3、多重背包(有限多个物品)

(1)多重背包一(朴素法)
与完全背包的朴素算法一样的,只要在多判断一下物品的数量不要超过给定的数量即可。

#include<iostream>
using namespace std;
const int N=110;
int n,m,v[N],w[N],s[N];
int f[N][N];
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%d%d%d",&v[i],&w[i],&s[i]);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			for(int k=0;k<=s[i]&&k*v[i]<=j;k++)
				f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
		}
	}
	printf("%d\n",f[n][m]);
	return 0;
}

(2)多重背包二(二进制优化)
思路:将每种物品进行分包,分为1,2,4,8...这样的每一份。为什么呢?
这样子分的话可以保证最后一定可以凑成原来总的数量,1,2它们可以凑成3;3,4又可以凑成7,1,4又可以凑成5,以此类推,最后这样划分一定可以凑成总的数量。划分完之后就成了0/1背包问题。

#include<iostream>
using namespace std;
const int N=1e6;//范围要注意!!!
int v[N],w[N],s[N];
int f[N],n,m,cnt;
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		int a,b,s;
		scanf("%d%d%d",&a,&b,&s);
		int k=1;
		while(k<=s)//二进制拆分背包
		{
			cnt++;
			v[cnt]=a*k;
			w[cnt]=b*k;
			s-=k;
			k*=2;
		}
		if(s>0)//没凑完整,剩下的继续凑
		{
			cnt++;
			v[cnt]=s*a;
			w[cnt]=s*b;
		}
	}
	for(int i=1;i<=cnt;i++)//0/1背包模型
	{
		for(int j=m;j>=v[i];j--)
			f[j]=max(f[j],f[j-v[i]]+w[i]);
	}
	printf("%d\n",f[m]);
	return 0;
}

(3)AcWing 6. 多重背包问题 III(0<N≤1000,0<V≤20000,0<vi,wi,si≤20000)单调队列优化
时间复杂度=1000*20000*log20000≈1e8

#include<iostream>
#include<cstring>
using namespace std;
const int N=20010;
int f[N],q[N],pre[N];
int n,m;
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=0;i<n;i++)
	{
		int v,w,s;
		scanf("%d%d%d",&v,&w,&s);
		memcpy(pre,f,sizeof(f));
		for(int j=0;j<v;j++)
		{
			int hh=0,tt=-1;
			for(int k=j;k<=m;k+=v)
			{
				if(hh<=tt&&k-q[hh]>s*v)//k-q[hh]>s*v说明超出了物品i的最大数量,所以不合法
					hh++;
				while(hh<=tt&&pre[q[tt]]-(q[tt]-j)/v*w<pre[k]-(k-j)/v*w)
					tt--;
				q[++tt]=k;
				f[k]=pre[q[hh]]+(k-q[hh])/v*w;
			}
		}
	}
	printf("%d\n",f[m]);
	return 0;
}

4、分组背包问题(每一组最多选一个)

思路:与0/1背包类似的,它只是把一些背包打包放在一个组里,但是在每一组里要么选一个要么不选,所以就成了0/1背包问题。所以只需要再加一层循环对每一组里的背包来判断选或不选,与0/1背包一样的思路。

#include<iostream>
#include<algorithm>
using namespace std;
const int N=110;
int s[N],v[N][N],w[N][N],f[N];
int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&s[i]);
		for(int j=1;j<=s[i];j++)
			scanf("%d%d",&v[i][j],&w[i][j]);
	}
	for(int i=1;i<=n;i++)
		for(int j=m;j>=0;j--)
			for(int k=0;k<=s[i];k++)
				if(j>=v[i][k])
					f[j]=max(f[j],f[j-v[i][k]]+w[i][k]);
	printf("%d\n",f[m]);
	return 0;
}

5. 混合背包问题

①0/1背包,完全背包,多重背包混合问题。
通过之前的计算可以发现,①多重背包是可以转换为0/1背包的,所以这两类可以一起处理;
②完全背包它在计算的过程是不用逆序的,这是它与0/1背包表达式的区别,所以没办法一起处理,所以它自己要单独处理。
总之,①多重背包转换为0/1背包,之后按0/1背包的方式处理;
②完全背包就正常处理。

#include<iostream>
using namespace std;
const int N=1010,M=5010;
int f[N];
int n,m;
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=0;i<n;i++)
	{
		int v,w,s;
		scanf("%d%d%d",&v,&w,&s);
		if(!s)//完全背包
		{
			for(int j=v;j<=m;j++)
				f[j]=max(f[j],f[j-v]+w);
		}
		else
		{
			if(s==-1) s=1;//0/1背包,把s变为1就可以和多重背包一样的处理方式了
			for(int k=1;k<=s;k*=2)
			{
				for(int j=m;j>=k*v;j--)
					f[j]=max(f[j],f[j-k*v]+k*w);
				s-=k;
			}
			if(s>0)//二进制分割后,还有剩下的
				for(int j=m;j>=s*v;j--)
					f[j]=max(f[j],f[j-s*v]+s*w);
		}
	}
	printf("%d\n",f[m]);
	return 0;
}

6.二维费用的背包问题(体积最多是j)

题意:有 N 件物品和一个容量是 V 的背包,背包能承受的最大重量是 M。每件物品只能用一次。
体积是 vi,重量是 mi,价值是 wi。求解将哪些物品装入背包,可使物品总体积不超过背包容量,
总重量不超过背包可承受的最大重量,且价值总和最大。
思路:f[i][j][k]表示从前i件物品中选且总体积不超过V,总重量不超过M的最大价值。
f[i][j][k]=max(f[i-1][j][k],f[i-1][j-v][k-m]+v)。

#include<iostream>
using namespace std;
const int N=110;
int f[N][N];
int n,v,w;
int main()
{
	scanf("%d%d%d",&n,&v,&w);
	for(int i=0;i<n;i++)
	{
		int a,b,c;
		scanf("%d%d%d",&a,&b,&c);
		for(int j=v;j>=a;j--)
			for(int k=w;k>=b;k--)
				f[j][k]=max(f[j][k],f[j-a][k-b]+c);
	}
	printf("%d\n",f[v][w]);
	return 0;
}

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值