您的位置:首页 > 其它

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

2018-02-14 21:33 274 查看
    


题目链接(点击打开链接
这一题很明显是一个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
;         //存储各个骨头的价值
int Vol
; //存储各个骨头所占的容量
int F

; //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
; //存储骨头信息

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

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”的处理。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息