背包九讲

2. 01背包问题

题意:有n个物品,背包总容量为v,第 i 个物品的体积为 v i v_i vi,价值为 w i w_i wi,求装入背包的最大价值
思路:n个物品,每个物品只能选一次。设 f [ i ] [ j ] f[i][j] f[i][j]表示前i个物品,体积不超过 j 的最大价值
分析:从右往左枚举体积,可以保证每一个 f [ i ] [ j ] f[i][j] f[i][j]是用 f [ i − 1 ] [ j − c ] f[i-1][j-c] f[i1][jc]转移过来的

#include <iostream>
using namespace std;
int n,m,c,v;
int f[1010];

int main()
{

	cin>>n>>m;
	for(int i=1;i<=n;++i)
	{	
		cin>>c>>v;
		for(int j=m;j>=c;--j)
			f[j]=max(f[j],f[j-c]+v);
	}
    cout<<f[m]<<endl;
	return 0;
}

3. 完全背包问题

题意:有n类物品,背包总容量为v,第i类物品的体积为 v i v_i vi,价值为 w i w_i wi,求装入背包的最大价值
思路:状态方程同上,f[i][j]表示前i类物品的体积不超过j的最大价值
分析:在01背包中,我们为了确保状态f[i][j]是从f[i-1][j-c]转移过来的,我们选择了从后往前遍历。而完全背包恰好相反,因为我们可以选择的物品是无限的。我们的状态 f [ i ] [ j ] f[i][j] f[i][j]应该是已经做过决策的 f [ i ] [ j − c ] f[i][j-c] f[i][jc]转移过来的。所以应该从前往后遍历

#include <iostream>
using namespace std;
int n,m,c,v;
int f[1010];

int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;++i)
	{
		cin>>c>>v;
		for(int j=c;j<=m;++j)
			f[j]=max(f[j],f[j-c]+v);
	}
	cout<<f[m]<<endl;
	return 0;
}

4. 多重背包问题 I

题意:有n类物品,背包总容量为v,第i类物品,有 s i s_i si件体积为 v i v_i vi,价值为 w i w_i wi,求装入背包的最大价值
思路:状态设置还是 f [ i ] [ j ] f[i][j] f[i][j]表示前i个物品总体积不超过 j 的最大价值
分析:其实也和01背包、完全背包没什么区别。只不过决策的次数变成了s次,物品、总体积、物品件数都在100以内。暴力枚举决策就好了

#include <iostream>
using namespace std;
int n,m,c,v,s;
int f[110];

int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;++i)
	{
		cin>>c>>v>>s;
		for(int j=m;j>=c;--j)
			for(int k=1;k<=s&&k*c<=j;++k)
				f[j]=max(f[j],f[j-k*c]+k*v);
	}
	cout<<f[m]<<endl;
	return 0;
}

5. 多重背包问题 II

题意:有n类物品,背包总容量为v,第i类物品,有 s i s_i si件体积为 v i v_i vi,价值为 w i w_i wi,求装入背包的最大价值
思路:状态设置还是 f [ i ] [ j ] f[i][j] f[i][j]表示前i个物品总体积不超过 j 的最大价值
分析:这里和上题的区别在于,n,m,s的范围在2000以内。继续暴力决策的话,明显会超时。因此可以考虑二进制优化,按二进制的方式,把一类物品拆分成多个物品

#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
int n,m,c,v,s;
int f[2010];

struct Thing
{
	int c,v;
};
vector<Thing> things;

int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;++i)
	{
		cin>>c>>v>>s;
		for(int k=1;k<=s;k*=2)
		{
			s-=k;
			things.push_back({c*k,v*k});
		}
		if(s>0)
			things.push_back({c*s,v*s});
	}
	
	for(auto it: things)
		for(int j=m;j>=it.c;--j)
			f[j]=max(f[j],f[j-it.c]+it.v);
	cout<<f[m]<<endl;

	return 0;
}

6. 多重背包问题 III

题意:有n类物品,背包总容量为v,第i类物品,有 s i s_i si件体积为 v i v_i vi,价值为 w i w_i wi,求装入背包的最大价值
思路:状态设置还是 f [ i ] [ j ] f[i][j] f[i][j]表示前i个物品总体积不超过 j 的最大价值
分析:这里n范围在1000,v的范围在20000,s的范围在20000以内。如果还是采用二进制优化的话。时间复杂度是: 1000 × l o g 2 ( 20000 ) × 20000 1000\times log_2(20000)\times 20000 1000×log2(20000)×20000,大约是3e8,会超时。所以可以采用单调队列优化的方法
优化思路:我们发现每一个 f [ i ] [ j ] f[i][j] f[i][j]其实都是从 f [ i − 1 ] [ j − c ] 、 f [ i − 1 ] [ j − 2 ∗ c ] 、 … 、 f [ i − 1 ] [ j − s ∗ c ] f[i-1][j-c]、f[i-1][j-2*c]、\dots、f[i-1][j-s*c] f[i1][jc]f[i1][j2c]f[i1][jsc]转移过来的,因此我们可以根据余数 r 分组,每一组是: r 、 r + c 、 r + 2 c 、 … r、r+c、r+2c、\dots rr+cr+2c,维护一个最大值单调队列,每次先判断入队,然后缩减区间,最后更新 f [ j ] f[j] f[j]

#include <iostream>
#include <cstring>
using namespace std;
const int maxn=20010;
int n,m,c,v,s;
int Q[maxn],f[maxn],g[maxn];

int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;++i)
	{
	    memcpy(g,f,sizeof(f));
		cin>>c>>v>>s;
		for(int r=0;r<c;++r)
		{
			int head=1,tail=0;
			for(int j=r;j<=m;j+=c)
			{
				while(head<=tail&&g[Q[tail]]-(Q[tail]-r)/c*v<=g[j]-(j-r)/c*v)
					tail--;
				Q[++tail]=j;
				
				while(head<=tail&&j-Q[head]>s*c) 
					head++;
				f[j]=max(f[j],g[Q[head]]+(j-Q[head])/c*v);	
			}	
		}
	}
	cout<<f[m]<<endl;
	return 0;
}

7. 混合背包问题

题意:n中物品,背包容量为m,有三类物品,第一类只能用一次,第二类能用无限次,第三类能用 s i s_i si次,物品的体积为 v i v_i vi,价值是 w i w_i wi,求背包能容纳的最大价值
思路:三种背包的混合,01背包和多重背包,可以分为一类处理

#include <iostream>
#include <vector>
using namespace std;
int n,m,c,v,s;
int f[1010];

struct Thing
{
	int f,c,v;
};
vector<Thing> things;

int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;++i)
	{
		cin>>c>>v>>s;
		if(s<0)
			things.push_back({-1,c,v});
		else if(s==0)
			things.push_back({0,c,v});
		else if(s>0)
		{
			for(int k=1;k<=s;k*=2)
			{
				s-=k;
				things.push_back({-1,k*c,k*v});
			}
			if(s>0)
				things.push_back({-1,s*c,s*v});
		}
	}
	
	for(auto it: things)
	{
		if(it.f==-1)
		{
			for(int j=m;j>=it.c;--j)
				f[j]=max(f[j],f[j-it.c]+it.v);
		}
		else
		{
			for(int j=it.c;j<=m;++j)
				f[j]=max(f[j],f[j-it.c]+it.v);
		}
	}
	cout<<f[m]<<endl;
	return 0;
}

8. 二维费用的背包问题

题意:有n件物品,容量为v,背包能承受的最大重量是m。每个物品只能用一次,体积是 v i v_i vi,重量是 m i m_i mi,价值是 w i w_i wi,求在不超过容量,不超过体积情况下,能够装入背包的最大价值。
思路:三维dp, f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k]表示前i中物品体积不超过j,重量不超过k,能获取的最大价值。同样的第一维可以省略。

#include <iostream>
using namespace std;
int n,m1,m2,c1,c2,v;
int f[110][110];

int main()
{
	cin>>n>>m1>>m2;
	for(int i=1;i<=n;++i)
	{
		cin>>c1>>c2>>v;
		for(int j=m1;j>=c1;--j)
			for(int k=m2;k>=c2;--k)
				f[j][k]=max(f[j][k],f[j-c1][k-c2]+v);	
	}
	cout<<f[m1][m2]<<endl;
	return 0;
}

9. 分组背包问题

题意:有n组物品,背包容量为v,每组的物品有若干个,但每组的物品只能选一个,求背包的最大价值
分析:其实多重背包是分组背包的一个特殊情况,分组背包中的每一个物品都是不一样的。所以只能暴力枚举每一个决策。而多重背包中因为物品相同,就可以做二进制优化、单调队列优化

#include <iostream>
using namespace std;
int n,m,s,c[110],v[100];
int f[110];

int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;++i)
	{
		cin>>s;
		for(int j=1;j<=s;++j)
			cin>>c[j]>>v[j];
		
		for(int j=m;j>=0;--j)
			for(int k=1;k<=s;++k)
				if(j>=c[k])
					f[j]=max(f[j],f[j-c[k]]+v[k]);	
	}
	cout<<f[m]<<endl;
	return 0;
}

10. 有依赖的背包问题

11. 背包问题求方案数

题意:在01背包的基础上,问取到最优的方案数有多少
思路:在f[j]在基础上需要再开一个g[j]数组记录路径数。然后我们所有的答案都是从f[0]、g[0]开始转移的

#include <iostream>
#include <cstring>
using namespace std;
const int mod=1e9+7;
int n,m,c,v;
//g[i]表示体积恰好是i的情况下的方案数 
//f[i]表示体积恰好是i的情况下的最大价值 
int f[1010],g[1010];

int main()
{
	memset(f,-0x3f,sizeof(f));
	f[0]=0,g[0]=1;
	
	cin>>n>>m;
	for(int i=1;i<=n;++i)
	{
		cin>>c>>v;
		for(int j=m;j>=c;--j)
		{
			if(f[j]<f[j-c]+v)
				g[j]=g[j-c];
			else if(f[j]==f[j-c]+v)
				g[j]=(g[j]+g[j-c])%mod;
			f[j]=max(f[j],f[j-c]+v);
		}	
	}
	int maxx=0,ans=0;
	for(int i=0;i<=m;++i)
		maxx=max(maxx,f[i]);
		
	for(int i=0;i<=m;++i)
		if(f[i]==maxx)
			ans=(ans+g[i])%mod;
	cout<<ans<<endl;	
	return 0;
}

12. 背包问题求具体方案

题意:在01背包的基础上求一个具体方案,要求字典序最小
思路:我们观察f[i][j]这个解,如果说f[i][j]=f[i-1][j]说明,我们不选,第i个物品可以得到最优解。如果说f[i][j]=f[i-1][j-c]+v,说明我们选择第i个物品得到最优解。当然可能,两种情况都相等,那就说明有两种最优解方案,取字典序最小的即可。
因为我们只能从得到的最优解,往前面推方案。所以我们在dp的时候要从第n个物品往第1个物品推,这样我们才能从第1个物品,即最优解出发,贪心地选择物品,对于前面的物品,能选则选

#include <iostream>
#include <cstring>
using namespace std;
const int mod=1e9+7;
int n,m,c[1010],v[1010];
int f[1010][1010];

int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;++i)
		cin>>c[i]>>v[i];
	
	for(int i=n;i>=1;--i)
	{
		for(int j=0;j<=m;++j)
		{
			f[i][j]=f[i+1][j];
			if(j-c[i]>=0)
				f[i][j]=max(f[i+1][j],f[i+1][j-c[i]]+v[i]);
		}		
	}
			
	int ans=f[1][m],vol=m;
	for(int i=1;i<=n;++i)
	{
		if(vol-c[i]>=0&&f[i+1][vol-c[i]]+v[i]==ans)
		{
			ans-=v[i];
			vol-=c[i];
			cout<<i<<" ";
		}
	}		
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值