HDU2602 Bone Collector(动态规划--01背包)

    

题目链接(点击打开链接

这一题很明显是一个01背包问题。

01背包的核心思想就是:依次考虑每一个物品,都有两种可能:拿或是不拿。

那么就得到一个核心方程:F[i,v]表示考虑第i个物品时,并且在所用容量不超过v的情况下,可以达到的最大价值。Wi表示第i个物品所占的容量。Vi表示第i个物品的价值。

那么就有F[i,v] = max(F[i-1,v],F[i-1,v-Wi]+Vi)

临界条件(初始值):F[0,v] = 0(不放任何物品时价值肯定为0)

下面给出伪代码:

    F[0,0...V] = 0;

    for i = 1 to Num

         for v = 0 to V

                   if v > Wi

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

                  else

                          F[i,v] = F[i-1,v]

下面以一个例子加以说明:

另外值得注意的一个问题是:初始化问题

一般题目①如果强调“要求恰好装满背包”,那么初始化时除了F[0,0] = 0,其余F[0,1...V] =  −∞ ;

②如果题目并没有强调“要求恰好装满背包”,那么所有F[0,0...V] = 0;

事实上,所谓初始化,就是在i=0的情况下,即不放物品的情况下的合法最优解。

因为在第一种问法的条件下,由于要求恰好装满背包,那么在不放物品的情况下,只有v=0时才合法,故只有F[0,0] = 0;

其余v!=0的情况均不合法,故将它们都赋值为−∞。

而在第二种问法下,由于并没有要求恰好装满,所以F[0,0...v]均为0

下面附上代码:

#include<bits/stdc++.h>
using namespace std;
#define N 1005
#define INF 0xffffff

int T;								//样例个数
int Num,V;							//骨头的个数,背包的总容量
int Val[N];						        //存储各个骨头的价值
int Vol[N];							//存储各个骨头所占的容量
int F[N][N];							//F[i][j]表示取第i个且容量不超过j的最大价值 

int main(){
	freopen("in.txt","r",stdin); 
	cin >> T;
	while(T--){
		memset(Val,0,sizeof(Val));			//初始化 
		memset(Vol,0,sizeof(Vol));
		memset(F,0,sizeof(F));
		cin >> Num >> V;
		for(int i = 1;i <= Num;++i){		        //输入各个骨头的价值 
			cin >> Val[i];
		}
		for(int i = 1;i <= Num;++i){			//输入各个骨头所占的容量 
			cin >> Vol[i];
		}
		for(int i = 0;i <= V;++i){			//赋初值,不放东西时价值肯定是零 
			F[0][i] = 0; 
		} 
		for(int i = 1;i <= Num;++i){			//动归的实现代码,对于每一个骨头,都考虑放还是不放 
			for(int v = 0;v <= V;++v){
				if(v >= Vol[i]) 
				F[i][v] = max(F[i-1][v],F[i-1][v-Vol[i]]+Val[i]);
				else 
				F[i][v] = F[i-1][v];
			}
		}
		cout << F[Num][V] << endl; 
	}
	return 0;
} 

优化

时间复杂度为O(Num*V),已经没法再优化了。但是空间复杂度还是可以再优化的,由于在考虑第i个物品时,是依赖于第i-1个物品的决策结果的,所以可以用“滚动数组”将二维数组优化为一维数组。

伪代码如下:

F[0] = 0;

for i = 1 to i;

  for v = V to Wi

      F[v] = max(F[v],F[v-Wi]+Vi);

ps:①max括号里面的F[v],此时仍然是i-1时得到的结果,相当于用二维数组时的F[i-1,v],同理,括号的F[v-Wi]相当于F[i-1][v-Wi]。

  ②由于F[v]依赖的两个是i-1时的F[v]和F[v-Wi],v和v-Wi均<=v,所以v要从后往前“滚动”,因为如果从前往后的话,例如先更新了F[Wi],那么在v继续递增的过程中,如果要用i-1时的F[Wi],可此时F[Wi]已经被更新为i时的值,所以就无法得到正确答案。

而从后往前的话就避免了这个问题,因为前面的F值不后依赖后面的F值的。

下面贴上代码:

#include<bits/stdc++.h>
using namespace std;
#define N 1005
#define INF 0xffffff

struct Stone{
	int Val;									//骨头的价值 
	int Vol;									//骨头所占的容量 
};

int Num,V;										//骨头的数量,背包的容量
Stone s[N];										//存储骨头信息

int F[N];										//用以记录最大价值 

int main(){
	freopen("in.txt","r",stdin);
	int T;										//样例个数
	cin >> T;
	while(T--){
		memset(s,0,sizeof(s));							//初始化 
		memset(F,0,sizeof(F));							//初始化的同时也对F进行了赋初值(F(0)=0) 
		cin >> Num >> V;
		for(int i = 1;i <= Num;++i){					//读入骨头信息 
			cin >> s[i].Val;
		}
		for(int i = 1;i <= Num;++i){
			cin >> s[i].Vol;
		}
		for(int i = 1;i <= Num;++i){					//运用动归实现,对于每一个骨头,考虑放还是不放 
			for(int v = V;v >= s[i].Vol;--v)
			     F[v] = max(F[v],F[v-s[i].Vol]+s[i].Val); 
		}
		cout << F[V] << endl;
	} 
	return 0;
}

PS:01背包是最简单、最基础的背包问题,但是也是基础,要认真理解。尽量使用第二种方法,就是说多用一维数组。

最后贴出背包九讲中的对01背包的讲解。

有一点需要注意就是在用二维数组时,我对背包九讲的伪代码做了一些小调整。就是当v<Wi时,此时F[i][v]应该是要赋值为F[i-1][v],当i=Num时,若V>Wi(即WNum,此时i=Num)时,加不加对“v<Wi”的处理,对答案是没有影响的,但是若是V<Wi,那么最后一行将不会有任何处理,最终答案将为0。以样例图加以说明就是,如果将最后一个物品的重量从5改为11后,那么i=3那一行都将变为0,最后输出答案F[3][10]=0,显然不对,所以我对那个伪代码加上了对“v<Wi”的处理。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值