Day6 动态规划(二)——初运用:01背包问题

背包问题——Knapsack

一、题目:01背包问题

有 N 件物品和一个最大承受重量是 W的背包。每件物品只能使用一次。
第 i 件物品的重量是 wi,价值是 vi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

输入格式
第一行两个整数,N,W,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 wi,vi,用空格隔开,分别表示第 i 件物品的重量和价值。

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

数据范围
0<N,W≤1000
0<vi,wi≤1000

在这里插入图片描述在这里插入图片描述

二、我的代码
(一)思路

根据上一次学的动态规划思路写的代码。
根据选不选最后一件找到关系函数后,写递归两部分(递归关系函数,递归出口。)
递归关系:
1)n件物品,书包总可承受重量total weight:t_w,记录物品重量weight的数组w[],记录物品价值value的数组v[]
2)v_max:前i件物品,在可承受重量下的可能的最大value的函数名。为了书写方便分析文字部分写成f
3)i表示在前i件中选择,j表示背包当前可承受的重量 ,
则,f(i,j) 等于:

  • 选第i件时,值为A=v[i]+v_f(i-1,j-w[i])
  • 不选第i件时,值为B=f(i-1,j)
    取A,B的max

最开始没有引入j变量,因为我当时按照上一篇中,把i描述成"可选择的物品为前i件",那么要装i件物品,背包的总称承重为t_w,似乎不是个变量。我们的递归过程i是逐渐变大的,随着新物品的加人必然会带来体积。所以i理解为,我们“只在前i件物品中选择”,想相当于我们在2^n种情况组成的大的集合的一个子集。

(二)代码
#include <iostream>
#include <algorithm>
using namespace std;
int w_max(int i,int t_w,int w[],int v[]);
int main(){
    int n,t_w,w[1000],v[1000];
    cin>>n>>t_w;
    for(int i=1;i<=n;i++){
        cin>>w[i]>>v[i];
    }
    cout<<v_max(n,t_w,w,v);
    return 0;
}
int v_max(int i,int j,int w[],int v[]){
    if(i==0) return 0;//递归出口
    else{//递归关系
        int A,B;
        A=w[i]+v_max(i-1,j-w[i],w,v);//A:选择第i件时,留给前i-1的重量只有t_w-w[i]
        B=v_max(i-1,j,w,v);//B:不选择第i件
        if(w[i]>j) return B;//A是有条件的,j作为形参会多次传入j-w[i]的值,对此-w[i]不断变小,所以他要是变到比当前w[i]小时,不能做A。
        else return max(A,B);
    }
}
(三)代码的验证:

1.验证结果
超时。
但是输入超时的数据,在devC中结果验证正确。
2.为什么超时
1)这样写完全是从结果往下算的,根本就没有
在这里插入图片描述

2)另一方面

粉色线左边的f的j部分都有-w[8],右边都没有-w[8]
粉色线分割后的左部分:黄色线左边都有-w[7],右边都无-w[7]
粉色线分割后的右部分:绿色线左边都有-w[7],右边都无-w[7]
这部分式子是不可能有重复的。除非碰上值相等,才会出现重复子问题

三、闫氏DP法
(一)DP动态规解决哪类问题

有限集合的最值或者个数或是否存在。用定义求,需要枚举,用DP优化。

(二)DP解决两阶段:

状态表示(划分子集属性:min,max,count)+状态计算(即关系
下面结合01背包,进行说明。
在这里插入图片描述
1.重点!是划分子集,从集合角度来分析问题,把一个大问题(大集合)分成一个个子集。
1)划分子集的原则:不遗漏(必须)+不重复(不一定,比如求最大值,子集之间重复也没啥关系)。
2)划分依据:找最后一个不同点
不好想,建议要多做题。

01背包问题,有N件商品,每件商品有选和不选两种可能,即状态为0或1,就是2^N种情况,有限集。暴力法就是用枚举法求出所有value取最大,复杂度高。
这个大集合分成选第i件和不选第i件这两个子集,每个子集还能细分子集。

2.属性就是 max min 等通常和题设有关。

四、提供的01背包写法:利用二维数组
(一)思路(以下文字解析,是参考B站正月点灯笼的作品:01背包问题)整理

关系的表示不通过 “函数+递归” 完成,而是通过 “循环+数组” 完成

1. 这里构造了一个数组,我们先看是怎样的一个数组
这是一个算01背包问题的网页,我把他截图,并做了一些标注。
i 表示: 只包含前i个
j 表示:背包的最大承重
数组值f [ i ][ j ]表示:“满足上述两个条件“ 的集合 的最大value。那么右下角 i=N,j=t_w 是题目所求。
在这里插入图片描述
2.怎么实现从“小下标” 到 “大下标” 从而实现动态规划?
就是从左到右,从上向下,填好这个数组,最后填到右下角,这个值就求出来了。
3.那根据什么算数组的值呢?
当然就是我们所说的那个关系!
在这里插入图片描述

(二)代码(参考yxc改写)
#include <algorithm>
#include <iostream>

using namespace std;

const int N=1010;

int n,t_w;//总可选件数n,总可提供的可承受最大重量t_w
int w[N],v[N];
int f[N][N];


int main(){
    cin>>n>>t_w;
    for(int i=1;i<=n;i++){
        cin>>w[i]>>v[i];
    }
    /*我们是先看只有一件再看有2件...n件。这相当于,一行一行写,即写完一行写下一行,则指向列的变量在内层变化。*/
    for(int i=1;i<=n;i++){//1行-n行
        for(int j=0;j<=t_w;j++){//0列-t_w列
            if(w[i]>j) f[i][j]=f[i-1][j];
            else f[i][j]=max(f[i-1][j-w[i]]+v[i],f[i-1][j]);
        }
    }
    cout<<f[n][t_w];
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值