浅说背包问题(上)

什么是背包问题

背包问题是线性DP的一个拓展,它的模型一般为:
有一个体积为V的背包,有n种物品,每种物品的数量有限或者无限,每个物体有它的属性(体积、质量等),问在不超过背包体积的情况下如何选择物品才能让物品的属性之和最大。

首先,很容易想到贪心是错误的,无论是从大到小贪,还是从小到大贪心的往背包里放物品,都可以找到反例。那么我们在这个地方就要考虑动态规划了

背包的分类

背包的类型有很多,比如最简单的01背包,还有稍微复杂一点的多重背包和完全背包以及混合背包,除此之外,还有更难的泛化背包,多维背包,组合背包等,本文先讲解最简单的01背包,至于其余的背包,其实都是从01背包去进行更改的

01背包

有N件物品和一个容量为V的背包。第i件物品的体积是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的体积总和不超过背包容量,且价值总和最大。

01背包特点:每种物品有且仅有一件,并且不可以再分。该物品要么取走,要么不取,只有两种状态,所以叫01背包。同时,01背包也是所有背包模型的基础。

思路一

每一种物品有两种状态(0和1),判断所有组成的方案,然后找出其中价值最大的。时间复杂度O(2^n).理论上是不可能实现的。

思路二

回溯法。时间复杂度和上面差不多。

思路三(重头戏)

动态规划
每种物品只有两种状态(0和1)。要求背包的最大价值。设前i件物品(部分/全部)放入体积为v的背包中的最大价值是 f [ i ] [ v ] f[i][v] f[i][v]

取走: f [ i ] [ v ] = f [ i − 1 ] [ v − c [ i ] ] + w [ i ] ; f[i][v]=f[i-1][v-c[i]]+w[i]; f[i][v]=f[i1][vc[i]]+w[i];
如果要把第i件物品放入背包中,说明背包中肯定空余第i件物品体积的位置。说明前(i-1)件物品占的体积为v-c[i],那么最大价值就是f[i-1][v-c[i]].既然把第i件物品放进去了,最大价值就+w[i].
不取: f [ i ] [ v ] = f [ i − 1 ] [ v ] ; f[i][v]=f[i-1][v]; f[i][v]=f[i1][v];
如果第i件物品不放入背包中,说明第i件物品是否存在对结果没有影响。前i-1件物品占用的最大体积就是v。最大价值就是f[i-1][v].
所以我们就可以得出这个状态转移方程: f [ i ] [ v ] = m a x ( f [ i − 1 ] [ v − c [ i ] ] + w [ i ] , f [ i − 1 ] [ v ] ) ; f[i][v]=max(f[i-1][v-c[i]]+w[i],f[i-1][v]); f[i][v]=max(f[i1][vc[i]]+w[i],f[i1][v]);
有了状态转移方程,那么我们就可以得出以下代码

常规代码

for(int i=1;i<=n;i++){//枚举所有物品
	for(int v=V,v>0,v--){//枚举范围内所有体积
		if(c[i]<=v){//如果该物品可以放进去
			f[i][v]=max(f[i-1][v-c[i]]+w[i],f[i-1][v]);
		}else f[i][v]=f[i-1][v];//如果该物品肯定放不进去
 	}
}

对于前i件物品,他的状态都是通过前i-1件物品转移过来的,所以v的范围无论是[0,V]还是[V,0]都没有任何关系。初始状态i=0的时候f[0][v]的值就是0,同样的,当v=0的时候,f[i][0]的值也仍然为0.所以不需要额外的初始化。

我们要求的n件物品放入V的背包中的最大价值就是f[N][V]的值

空间优化

不难想到,如果N的值比较大的话,很容易爆空间,那么我们就要想办法进行优化,可以发现,我们的这个状态转移方程只和 v − c [ i ] v-c[i] vc[i]有关,那么我们就可以使用滚动数组进行优化
但是这里要注意,如果我们采用自滚的话,在枚举v的时候就必须从大往小去枚举,因为我们可以发现在计算dp[j]的答案时,会用到dp[j-v[i]]的答案,如果j是正着枚举,dp[j-v[i]]的答案就是已经更新过的,答案是错误的;但是如果我们把j倒着枚举,dp[j]会比dp[j-v[i]]先更新,那么用到的dp[j-v[i]]的值就是上一层的,也就是我们需要的答案。所以01背包如果用一维数组表示,背包体积j的枚举只能倒着枚举,否则答案就是错误的。

for(int i=1;i<=n;i++){
	for(int j=m;j>=v[i];j--){
		dp[j]=max(dp[j-v[i]]+w[i],dp[j]);
	}
}

例题讲解

[NOIP2001 普及组] 装箱问题

题目描述

有一个箱子容量为 V V V,同时有 n n n 个物品,每个物品有一个体积。

现在从 n n n 个物品中,任取若干个装入箱内(也可以不取),使箱子的剩余空间最小。输出这个最小值。

输入格式

第一行共一个整数 V V V,表示箱子容量。

第二行共一个整数 n n n,表示物品总数。

接下来 n n n 行,每行有一个正整数,表示第 i i i 个物品的体积。

输出格式

  • 共一行一个整数,表示箱子最小剩余空间。

样例 #1

样例输入 #1
24
6
8
3
12
7
9
7
样例输出 #1
0

提示

对于 100 % 100\% 100% 数据,满足 0 < n ≤ 30 0<n \le 30 0<n30 1 ≤ V ≤ 20000 1 \le V \le 20000 1V20000

【题目来源】

NOIP 2001 普及组第四题

思路

这道题其实就是一道裸的01背包问题,可以直接解决

#include<bits/stdc++.h>
using namespace std;

int v[20100],dp[20100];
int main(){
	int V,n;
	cin>>V>>n;
	for (int i=1;i<=n;i++){
		cin>>v[i];
	}
	for (int i=1;i<=n;i++){
		for (int j=V;j>=v[i];j--){
			dp[j]=max(dp[j],dp[j-v[i]]+v[i]);
		}
	}
	cout<<V-dp[V];
	return 0;
}

最大约数和

题目描述

选取和不超过 S S S 的若干个不同的正整数,使得所有数的约数(不含它本身)之和最大。

输入格式

输入一个正整数 S S S

输出格式

输出最大的约数之和。

样例 #1

样例输入 #1
11
样例输出 #1
9

提示

【样例说明】

取数字 4 4 4 6 6 6,可以得到最大值 ( 1 + 2 ) + ( 1 + 2 + 3 ) = 9 (1+2)+(1+2+3)=9 (1+2)+(1+2+3)=9

【数据规模】

对于 100 % 100 \% 100% 的数据, 1 ≤ S ≤ 1000 1 \le S \le 1000 1S1000

思路

这题很容易想到搜索,预处理出来每一个数的约数的数量,然后爆搜,但是数据范围很大。既然每一个数字都有两种状态,选或者不选,而且任意两个数字之间也没联系,所以可以直接考虑背包,题目中说了不同的正整数,所以就是一个裸的01背包。先预处理出来[1,n]中每个数的约数的数量,再以n作为背包体积,数字1—n作为体积,约数之和作为价值跑一遍01背包即可。

#include<bits/stdc++.h>
using namespace std;

int a[5100],dp[5100];
void yueshu(int k){
	for (int i=1;i<k;i++){
		if (k%i==0)a[k]+=i;
	}
	return;
}
int main(){
	int s;
	cin>>s;
	for (int i=1;i<=s;i++){
		yueshu(i);
	}
	for (int i=1;i<=s;i++){
		for (int j=s;j>=i;j--){
			dp[j]=max(dp[j],dp[j-i]+a[i]);
		}
	}
	cout<<dp[s];
	return 0;
}

请添加图片描述
请添加图片描述
请添加图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值