Dynamic Programming从入门到放弃 —— 第四章:详解0 / 1背包

Dynamic Programming之0/1背包问题

假设有一个小偷,他带了一个背包想偷东西,这个背包的容量capacity = 8。他可以偷的物品一共有四个,每个物品都有自己的价值和重量,每个物品都可以选择放或者不放(放就得到1个物品,不放就得到0个物品,因此得名0/1背包)。那么,在背包可承受的最大重量为8的前提下,如何选取要偷的物品,使得所偷物品的总价值最大?即求MAX(总价值)

背包的基本模型:
背包是最典型、最基本的DP问题。给你一个容量为V的背包和若干种物品,在一定的限制条件下(每种物品都占用一定容量),问最多能放进多少价值的物品?即求价值的最优解

下表是每个物品的价值和重量:

在这里插入图片描述
首先引入一下 pack( i , j ) 的意思:
pack( i , j )表示考虑从第1件到第 i 件物品,在背包承受能力为 j 的情况下,能拿到物品的最大值。

如:pack(4,8)表示从第一件物品到第四件物品全考虑进来,在背包容量为8的情况下能拿到的最大价值。

pack(3,8)表示从第一件物品到第三件物品考虑进来,在背包容量为8的情况下能拿到的最大价值。即不考虑第四件物品的情况下的MAX(总价值)

pack(2,7)表示只考虑前两件物品,且背包容量为7的情况下能拿到的最大价值。

pack(1,0)表示只考虑第一件物品,且背包容量为0的情况下能拿到的最大价值。很容易看出pack(1,0) = 0 。因为背包的可承受重量为0,即不能放下任何一个物品(买了个假包哈哈)

pack(0,0)表示不考虑任何一件物品且背包的容量也为0(可以理解为小偷就来溜个弯不打算偷东西或者他发现自己买了个假包所以放弃了偷东西。嗯?会不会是先来打探敌情?)

pack(1,1)表示只考虑第一件物品,且背包容量为1的情况下能拿到的最大价值。而第一件物品的重量为2,故易得pack(1,1 )= 0。

pack(1,2)表示只考虑第一件物品,且背包容量为2的情况下能拿到的最大价值。这种情况表示可以拿走第一件物品,故pack(1,2)为第一件物品的价值,即pack(1,2) = 1。

… …

当然,我们的目的是求出pack(4,8)的值。而要求出pack(4,8),就要先求出pack(0,0),pack(0,1),pack(0,2)… …pack(1,0),pack(1,1),pack(1,2)… … 最终根据简单的情况得出复杂的结果pack(4,8),这也是动态规划的核心思想

(重要!!!)01背包状态转移方程:

在这里插入图片描述
下面对pack(i-1,j-w[i])+ price[i]做一个更详细的解释:
假设初始要从第一件物品考虑到第四件物品,且背包的容量为7,即pack(4,7)。现在选择第四件物品进入背包,那么之后就要继续考虑前三件物品,用 i - 1 来表示。且第四件物品进入背包后,背包的总容量需要减去第四件物品的重量,用 j-w[i] 来表示,即7 - weight[ 4 ] = 7 - 5 = 2。最后,再加上第四件物品的价值。

按此方法依次填写下面表格,最终可以求出pack(4,8)

在这里插入图片描述

对于表格第一行,由于 i 恒等于0,表示不考虑物品,故第一行恒等于0。

对于表格第一列,由于 j 恒等于0,表示背包的容量为0,故第一列也恒等于0。

博客上面已经写到pack(1,1) = 0 , pack(1,2) = 1

故初步填写表格如下:
在这里插入图片描述

对于pack(1,3),可以使用公式计算:
由于此时背包的容量为3,第一件物品的重量为2,可以放下第一件物品,所以

pack(1,3)=
= max{ pack(i - 1,j) , pack(i - 1,j - w[i]) + price[i] }
= max{ pack(0,3) , pack(0,3 - w[1]) + price[1]}
= max{pack(0,3),pack(0,3 - 2) + 1}
= max{0,pack(0,1) + 1}
= max{0,1}
= 1

同理可填完表格:得到pack(4,8) = 8
在这里插入图片描述
01背包代码实现如下:

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>

using namespace std;

int w[5] = {0,2,3,4,5}; //重量数组,表示四件物品的重量
int v[5] = {0,1,2,5,6}; //价值数组,表示四件物品的价值
int f[5][9] = {0}; //二维数组

int main(){
    //用i来遍历物品,j来遍历背包容量
    for (int i = 1;i <= 4;i++){
        for (int j = 1;j <= 8;j++){
            //如果当前物品的重量比背包的容量还大,那么背包重量不变,但是只考虑前i - 1个物品
            if (w[i] > j) f[i][j] = f[i - 1][j];
            else f[i][j] = max(f[i - 1][j],f[i - 1][j - w[i]] + v[i]);
        }
    }
    
    //输出二维数组
    for (int i = 0;i <= 4;i++){
        for (int j = 0;j <= 8;j++){
            printf("%d ",f[i][j]);
        }
        puts(" ");
    }
    return 0;
}

在这里插入图片描述

模板题
原题链接
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。

第 i 件物品的体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。

接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。

输出格式
输出一个整数,表示最大价值。

数据范围
0<N,V≤1000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
8

AC代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1010;
int f[N][N];
int w[N];//体积数组
int v[N];//价值数组

int main(){
    int n,m;//物品数量和背包容量
    scanf("%d%d",&n,&m);
    
    //初始化体积和价值数组
    for (int i = 1;i <= n;i++) scanf("%d%d",&w[i],&v[i]);
    
    //i用来遍历物品,j用来遍历容量
    for (int i = 1;i <= n;i++){
        for (int j = 1;j <= m;j++){
            //状态转移方程
            if (w[i] > j) f[i][j] = f[i - 1][j];
            else f[i][j] = max(f[i - 1][j],f[i - 1][j - w[i]] + v[i]);
        }
    }
    
    printf("%d",f[n][m]);
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值