01背包详解(ZeroOnePack)

有N件物品,一个容量为V的背包。放入第i件物品耗费的空间是Ci,得到的价值是Wi。

为了便于阅读,本文把F[i][v]写为F[i, v]。

F[i, v] = max(F[i - 1, v], F[i - 1, v - Ci] + Wi)

“将前i件物品放入容量为v的背包中”这个子问题,若只考虑第i件物品的策略(放或不放)。

那么就可以转化为一个只和前i - 1件物品相关的问题。

如果不放第i件物品,那么问题就转化为“前i - 1件物品放入容量为v的背包中”,价值为F[i - 1, v];

如果放第i件物品,那么问题就转化为“前i - 1件物品放入剩下的容量为v - Ci的背包中”,此时能获得

的最大价值就是F[i - 1, v - Ci] + Wi

例如F[2, v] = MAX(F[1, v], F[1, v - C[2]] + W[2])
前2件物品放入容量为v的背包中,如果放第2件物品,问题就转化为“第1件物品放入容量为v的背包中”;如果不放第2件物品,问题就转化为“第1件物品放入容量为v - C[2]的背包中”。其中v要枚举至V,因为我们不能确定v的值。

代码如下:

/*
	01背包问题,时间空间复杂度都为O(V*N)
	F[i, v] = max(F[i - 1, v], F[i - 1, v - Ci] + Wi)
*/
#include <iostream>
using namespace std;
const int num = 100;
int main()
{
	//------输入------
	int N, V;			//有N件物品,一个容量为V的背包
	cin >>N >>V;
	int C[num],W[num];	//放入第i件物品耗费的空间是C[i],得到的价值是W[i]。
	for(int i = 1; i <= N; i++)
		cin >>C[i] >>W[i];
	int F[num][num * 10];
	memset(F, 0, sizeof(F));
	//------处理------
	for(int i = 1; i <= N; i++)
		for(int v = C[i]; v <= V; v++)
			F[i][v] = F[i - 1][v] > F[i - 1][v - C[i]] + W[i] ? F[i - 1][v] : F[i - 1][v - C[i]] + W[i];
	//------输出------
	cout <<F[N][V] <<endl;
	return 0;
}

空间时间复杂度为O(V*N),可以进一步的优化空间复杂度至O(V)。

首先要知道,我们之前是通过i从1到N的循环来依次表达前i件物品存入的状态。
即i = 1..N,每次计算出二维数组F[i, C[i]..V]的所有值。

那么,如果只用一个数组F[0..V],能不能保证第N次循环结束后F[v]就是我们定义的状态F[N, v]呢?

F[i, v]是由F[i - 1, v]和F[i - 1, v - C[i]]两个子问题递推而来。

能否保证在推F[v]时,能够取用类似F[N - 1, v]和F[N - 1, v - C[N]]的值呢?

分析一下F[v]的含义,表示把前i件物品放入容量为v的背包里所能得到的最大价值。

假设第i件物品不放,则F[v]的含义转化为“把前i - 1件物品放入容量为v的背包里所能得到的最大价值”。

假设第i件物品要放,则问题转化为F[v - C[i]]。

根据前面的递推,F[v]在被更新前(即假设第i件物品不放)相当于F[i - 1, v],我们假设V > v1 > v2 > C[i]。

如果v = C[i]..V,会有一种情况,当v2被物品i更新一次,v1时又由F[v2]与背包更新一次,这样相当于物品i被使用了2次,与题意不合。

所以逆序是关键!

for(i = 1; i <= N; i++)
  for(v = V; v >= C[i]; v--)
    F[v] = max(F[v], F[v - C[i]] + W[i]);

改进后的代码:

/*
	01背包问题优化,空间复杂度为O(V)
	F[v] = max(F[v], F[v - Ci] + Wi)
*/
#include <iostream>
using namespace std;
const int num = 100;
#define EMPTY 0	//当背包要求装满时填-65536
int main()
{
	freopen("test.in","r",stdin);
	//------输入------
	int N, V;			//有N件物品,一个容量为V的背包
	cin >>N >>V;
	int C[num],W[num];	//放入第i件物品耗费的空间是C[i],得到的价值是W[i]。
	for(int i = 1; i <= N; i++)
		cin >>C[i] >>W[i];
	int F[num * 10];
	for(int i = 0; i <= V; i++)
		F[i] = EMPTY;
	//当EMPTY = -65536时
	//F[0] = 0;
	//------处理------
	for(int i = 1; i <= N; i++)
		for(int v = V; v >= C[i]; v--)
			F[v] = F[v] > F[v - C[i]] + W[i] ? F[v] : F[v - C[i]] + W[i];
	//------输出------
	cout <<F[V] <<endl;
	return 0;
}

初始化细节问题

一般有两种问法,第一种没有要求把背包装满,初始化时应该把F[0..V]全部设为0。这个可以理解成,对于任何容量的背包都有一个合法的解“什么都不装”。

第二种要求背包恰好装满,则F[0] = 0以外F[1..V]全部设为-∞,这样就能保证最终得到的F[V]是一种恰好装满背包的最优解。这个可以理解成,只有容量为0的背包可以在“什么都不装”的情况下被恰好装满,其他容量均没有合法的解,属于未定义状态。

 

理解后可去http://acm.hdu.edu.cn/showproblem.php?pid=2602测试一下,属于不要求把背包装满的问题。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值