WOJ 1005

3 篇文章 0 订阅

01背包

前言
 背包问题本身已经是老生常谈了,但是这依旧是一个十分经典的问题,初学动态规划的朋友们肯定都绕不过它。网上已经有了很多十分优秀的背包问题讲解,譬如经典的《背包九讲》,但是我自己学了背包之后发现一个问题就是理解不够透彻,见到一个问题学一次,学一次就能刷几道题。现在我自己重新做了一些算法题,遇到了简单的01背包依旧不知道怎么做,于是就凭借零散的记忆和自我理解,对这些问题进行了求解,在这里把我的理解写下来,一方面如果能够帮助到其他人也算是一件善事,另一方面也算是一种成长记录。


正文
 01背包的表述为有n 个物品,它们有各自的重量和价值,现有给定容量的背包,如何让背包里装入的物品具有最大的价值总和?
 此处假设第i个物品的花费为c[i],价值为v[i];则对于任一个物品对问题的总贡献只能是0或者是v[i],即取或者不取;设在当前最大背包容量为V的情况下,考虑第i个物品的贡献值时的最优解为

f[i][V]=max{ f[ i-1 ][ V ] , f[ i-1 ][ V-c[ i ] ]+v[ i ]}

 式中f[i][V]表示i个物品在容量为V的情况下的最优解。 有了动规方程,接下来就是确定平凡解。 容易知道当容量为0时,不论物品的价值如何,最优解都是0,即f[i][0]恒等于0;另外当只有一个物品的时候,如果一个物品能放进背包里,那么不论背包容量有多大,最优解也只能为这个物品的价值,即f[0][val]为0或者v[0]。

看到这里可能会有一点抽象,那么我们来进行一个简单的模拟,假如背包容量V=4,c[0]=2,v[0]=2;c[1]=1,v[1]=1.
由上面的分析可以得到如下表格:

    i\v01234
000333
101344
由表格可以看到第0行第0列都是平凡解,只有第1行是需要求值的,f[1][1]时,取放入第1个物品和不放入第1个物品的最大值,即max{ f[0][1], f[0][1-c[1]]+v[1] },后面的值同理可以求出来。

效率分析
 时间复杂度上易知,需要遍历整个表格,所以时间复杂度是O(n*V),V是背包大小,n是物品个数。空间复杂度的上界是O(n*V)。一般来说当背包容量达到10^5的时候空间复杂度就难以忍受了,但是时间上还有余裕。那么如何进行空间优化呢?
 如果通过上面的例子求解和动规方程,聪明的你就能发现,下一行的值只跟上一行的值有关,即如果我们的目标是计算最终结果而不需要知道中间值的话,那么剩下的那些都是无用数据。所以我们只需要存储两行,计算的时候让其滚动即可。
 下面附上一道whuoj的题,贴上代码,注释部分是朴素解。

#include <iostream>

using namespace std;

const int maxn = 102;
const int V=100005;

int c[maxn],v[maxn];

//int f[maxn][V];
int f1[V],f2[V];


int main(){
	int n,maxv;
	while(cin>>n){
		for(int val=0;val<V;val++){
			f1[val]=0;f2[val]=0;
		}
		for(int i=0;i<n;i++){
			cin>>c[i]>>v[i];
			if(i-c[i]>=0)
				f1[i]=v[i];
			else f1[i]=0;
		}
		cin>>maxv;
		f2[0]=0;
		for(int i=0;i<n;i++){
			for(int val=1;val<=maxv;val++){
				if(val-c[i]>=0&&(f1[val-c[i]]+v[i])>f1[val])
					f2[val]=f1[val-c[i]]+v[i];
				else f2[val]=f1[val];
			}
			for(int val=0;val<=maxv;val++)
				f1[val]=f2[val];
		}
		cout<<f2[maxv]<<endl;
		/*cin>>maxv;
		for(int val=0;val<=maxv;val++)
			if(val-c[0]>=0)
				f[0][val]=v[0];

		for(int i=1;i<n;i++)
			for(int val=1;val<=maxv;val++)
				if((val-c[i])>=0&&(f[i-1][val-c[i]]+v[i])>f[i-1][val])
					f[i][val]=f[i-1][val-c[i]]+v[i];
				else
					f[i][val]=f[i-1][val];

		for(int i=0;i<n;i++){
			for(int v=0;v<=maxv;v++)
				cout<<f[i][v]<<" ";
			cout<<endl;
		}
		cout<<f[n-1][maxv]<<endl;*/
	}
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值