C++01背包模板

我是蒟蒻!!!

前言

由于此篇为完全背包(C++完全背包模板-CSDN博客),多重背包(C++多重背包模板-CSDN博客)等的基础,本人在此由浅入深提供3+1种写法

01背包题解【模板】:

题目描述

有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。

第 i 件物品的体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大
输出最大价值。

输入格式

第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。

输出格式

输出一个整数,表示最大价值。

数据范围

0<N,V≤1000
0<vi,wi≤1000

输入 #1

4 5
1 2
2 4
3 4
4 5

输出 #1

8

问题解释

问题很简单,不过看完后,你也许会产生一个想法:贪心,求出每个物品单位体积内的价值di,并按照其排序,求出最大值,不过我们很容易举出一个反例

假如 N=3,V=100

v1=50,w1=50   d1=1

v2=50,w2=50   d2=1

v3=51,w2=99   d3=99/51

显然,d3>d1=d2 所以我们应该选择物品3,不过正解为,选择物品1,2,总价值50+50>99,所以我们不能选择贪心,综上我们选择01搜索来求解最大值

01搜索:

一个物品,我们可以选也可以不选,所以我们可以写出二叉搜索

Ac代码如下:

#include <bits/stdc++.h>
using namespace std;
int n,V,ans;
int w[1005],v[1005];
void dfs(int i,int value,int vl)//第i个数,目前的价值,剩余的体积 
{
	if (i==n)//选完了 
	{
		ans=max(ans,value);
		return ;
	}
	if (vl-v[i+1]>=0) dfs(i+1,value+w[i+1],vl-v[i+1]);//选,总价值加wi,剩余体积减vi
	dfs(i+1,value,vl);//不选,价值体积都不变 
}
int main ()
{
	int i,j;
	cin>>n>>V;
	for (i=1;i<=n;i++)
	{
		cin>>v[i]>>w[i];
	}
	if (V-v[1]>=0) dfs(1,w[1],V-v[1]);//选,同上 
	dfs(1,0,V);//不选,同上 
	cout<<ans;
	return 0;
}

因为这个搜索只有两种选法,操作简单,但是层数多,容易爆栈,所以我们可以选择运用递推

01背包无优化:

我们用一个数组f[i][j]表示前i个物品用了小于等于j个单位空间所存储的最大价值

我们可以从答案的来源f[N][V]得知,f[N][V]由f[N-1][V](不选)和f[N-1][V-v[N]]+w[N](减去第N个物品的体积,再将累计的价值加上第N个物品的价值)中较大的那个值得出

所以,我们可以从第N个物品类推第N-1个物品:f[N-1][V]=max(f[N-2][V],f[N-2][V-v[N-1]]+w[N-1])

我们又可以从第N个物品类推V-1体积时的情况:f[N][V-1]=max(f[N-1][V-1-v[N]]+w[N])

发现规律了吗?f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i])   (其中j-v[i]>=0)

Ac代码如下:
#include <iostream>
#include <cstdio>
using namespace std;
int f[1005][1005];
int v[1005],w[1005];
int n,V;
int main ()
{
	int i,j;
	scanf("%d%d",&n,&V);
	for (i=1;i<=n;i++) scanf("%d%d",&v[i],&w[i]);
	for (i=1;i<=n;i++)
	{
		for (j=1;j<=V;j++)
		{
			if (j-v[i]>=0) f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
			else f[i][j]=f[i-1][j]; 
		}
	}
	printf("%d",f[n][V]);
	return 0;
}

f[1000][1000]太大了,有没有什么方法优化空间呢?

01背包一维优化

我们发现f[i][j]只和第i-1层和本层的数值有关,所以我们不必开1000*1000的数组,可以将i%2,进行两层之间的传递

核心代码如下:
for (i=1;i<=n;i++)
{
	for (j=v[i];j<=v;j++)
	{
		f[i%2][j]=max(f[(i+1)%2][j],max(f[i%2][j-v[i]]+w[i],f[i%2][j-1]));
        //不选 选 传递前i个数的最大值 
	}
} 

我们又可以发现,假如只用一层的一维数组,如果不进行修改的话,已经是第i-1层的最优值了,但如果按照前面的方法,就会对已有的i-1层数值进行修改,如f[i][j-v[i]]可能已经被改为这层的最大值,不满足只用一次

那么,f[i][j-v[i]]一定是前面的值对后面的值有影响(j-v[i],j不断增大,v[i]在本层不变),所以我们采用倒序循环就能完美的避免这个问题!

Ac代码如下:
#include <bits/stdc++.h>
using namespace std;
int n,V,ans;
int v[1005],w[1005],f[1005];
int main ()
{
	int i,j;
	cin>>n>>V;
	for (i=1;i<=n;i++) cin>>v[i]>>w[i];
	for (i=1;i<=n;i++)
	{
		for (j=v[i];j<=m;j++)
		{
			f[j]=max(f[j],max(f[j-v[i]]+w[i],f[j-1]));
		}
	}
	cout<<f[V];
	return 0;
}

成功Ac,完结撒花!

测试参考题目:P1048 [NOIP2005 普及组] 采药 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

(注意将输入输出,数组大小进行修改)

如有不解或优化,可在评论区留言

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值