数据结构算法系列----背包问题(01,完全,多重)

一、01背包

1、01背包介绍

        "01背包"是一个经典的动态规划问题。在01背包中,给定一个背包容量和一组物品,每个物品都有自己的重量和价值。问题的目标是选择一些物品放入背包中,使得放入的物品总重量不超过背包容量,同时使得放入的物品总价值最大。

        "01"表示每种物品最多 只能选择一次,即要么放入背包中,要么不放入。这是与其他背包问题(如完全背包和多重背包)的区别之一。 

2、01背包核心代码

   我们设背包容量为w,每个物品的质量和价值分别为wi,vi 。 我们把这个容量为w的背包分成w份存储在dp数组中,分别是1,2,3......w,代表这当背包容量为1,2,3....w时这个背包最大最多能装多少价值的东西: 

​
for(int i=0;i<n;i++){ //遍历每一个物品
  for(int j=w;j>=ww[i];j--){ // 从最大容量向下遍历
      dp[j]=max(dp[j],dp[j-ww[i]]+v[i];  //状态转移方程(得到每个容量的最大价值)
  }
}

​

二、完全背包

1、完全背包解释

   完全背包问题是另一种常见的背包问题,与01背包问题不同的是,在完全背包问题中,每种物品可以选择 无限次 放入背包中。也就是说,对于每种物品,可以选择放入0个、1个、2个,直至放入尽可能多个,只要总重量不超过背包的容量。完全背包问题与01背包问题的主要区别在于状态转移方程的变化。

2、完全背包核心代码

for(int i=0;i<n;i++){
    for(int j=ww[i];j<=w;j++){
        dp[j]=max(dp[j,dp[j-ww[i]]]+v[i]);
    }
}
  • 外层循环for(int i=0;i<n;i++)遍历每个物品i
  • 内层循环for(int j=ww[i];j<=w;j++)从背包容量ww[i]开始,逐步遍历背包容量w,更新背包容量为j时的最大价值。
  • 在内层循环中,通过状态转移方程dp[j]=max(dp[j], dp[j-ww[i]]+v[i])来更新背包容量为j时的最大价值。
  • 其中,dp[j]表示当前背包容量为j时的最大价值,dp[j-ww[i]]+v[i]表示将第i个物品放入背包后的总价值,取两者的最大值作为更新后的最大价值。

     通过动态规划的方式填充dp数组,最终得到dp[w]即为问题的解,表示背包容量为w时放入所有物品所能获得的最大价值。

3、完全背包进阶(背包放满的最大价值)

1、核心代码:

 memset(dp, -0X3f, sizeof(dp));
    dp[0] = 0;
    for (int i = 1; i <= n; i++)
        for (int j = v[i]; j <= V; j++)
            dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
    if(dp[V]<0) dp[V]=0;
  • memset(dp, -0X3f, sizeof(dp));:将dp数组中的所有元素初始化为一个较大的负值(这里使用-0X3f),表示初始时背包中的最大价值为负无穷。
  • dp[0] = 0;:将背包容量为0时的最大价值初始化为0。
  • for (int i = 1; i <= n; i++):遍历每个物品i
  • for (int j = v[i]; j <= V; j++):从当前物品i的价值v[i]开始,逐步遍历背包容量V
  • dp[j] = max(dp[j], dp[j - v[i]] + w[i]);:更新背包容量为j时的最大价值,取当前最大价值dp[j]与放入第i个物品后的总价值dp[j - v[i]] + w[i]的较大值。
  • if(dp[V]<0) dp[V]=0;:如果背包容量为V时的最大价值为负值,则将其设为0,表示背包中没有物品时的最大价值为0。

  最终,通过动态规划的方式填充dp数组,得到dp[V]即为问题的解,表示背包容量为V时放入所有物品所能获得的最大价值。

2、例题

例题链接:https://ac.nowcoder.com/acm/problem/226516

完整ac代码:

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int N = 1010;
const int INF = 0x3f3f3f3f;
int n, V, v[N], w[N], dp[N];

int main()
{
    cin >> n >> V;
    for (int i = 1; i <= n; i++)
    {
        cin >> v[i] >> w[i];
    }

    memset(dp, 0, sizeof(dp));
    for (int i = 1; i <= n; i++)
        for (int j = v[i]; j <= V; j++)
            dp[j] = max(dp[j], dp[j - v[i]] + w[i]);

    cout << dp[V] << endl;

    memset(dp, -0X3f, sizeof(dp));
    dp[0] = 0;
    for (int i = 1; i <= n; i++)
        for (int j = v[i]; j <= V; j++)
            dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
    if(dp[V]<0) dp[V]=0;
    cout << dp[V] << endl;
    
    return 0;
}

      这里有个要注意在使用memset函数初始化数组时,传入的负数值不能太小。因为memset函数将传入的值按字节进行复制,如果传入的是太小的负数,可能会导致在某些编译器中出现意外的行为。

  比如在第二个memset之中不用0x3f用 -1000就会错乱。


三、多重背包

1、多重背包介绍

      多重背包问题是背包问题的一个变种,与0-1背包和完全背包不同,多重背包问题中每种物品的数量是有限的,即可以选择多个物品,但每种物品的数量是有限的。


2、核心代码

for(int i=0;i<n;i++){
        int k=1;
        int x,w,v;
        cin>>x>>w>>v;
        while(k<=x){ //根据每种物品的数量进行了二进制拆分,有效地处理了每种物品的数量限制。
            cnt++;
            ww[cnt]=w*k;
            vv[cnt]=v*k;
            x-=k;
            k<<=1;
        }
        if(x){
            cnt++;
            ww[cnt]=w*x; 
            vv[cnt]=v*x; 
        }
    } //拆分之后用01 背包即可
    for(int i=1;i<=cnt;i++){
        for(int j=t;j>=ww[i];j--){
            dp[j]=max(dp[j],dp[j-ww[i]]+vv[i]);
        }
    }

      对这段这个二进制拆分(可以把这个和我之间写的快速幂一起理解,有点类似)有疑惑的同学可以参考B站:https://www.bilibili.com/video/BV1MA41177cg/?spm_id_from=333.1007.top_right_bar_window_history.content.click

3、例题

   链接:https://ac.nowcoder.com/acm/problem/235950

完整代码:

#include<bits/stdc++.h>
using namespace std;
const int N=100006;
int main(){
    int n,t;
    int ww[N]={0},vv[N]{0},dp[N]={0};
    int cnt=0;
    cin>>n>>t; 
    for(int i=0;i<n;i++){
        int k=1;
        int x,w,v;
        cin>>x>>w>>v;
        while(k<=x){
            cnt++;
            ww[cnt]=w*k;
            vv[cnt]=v*k;
            x-=k;
            k<<=1;
        }
        if(x){
            cnt++;
            ww[cnt]=w*x; 
            vv[cnt]=v*x; 
        }
    }
    for(int i=1;i<=cnt;i++){
        for(int j=t;j>=ww[i];j--){
            dp[j]=max(dp[j],dp[j-ww[i]]+vv[i]);
        }
    }
    cout<<dp[t]<<endl;
    return 0;
}

for(int i=0;i<n;i++){
    for(int j=ww[i];j<=w;j++){
        dp[j]=max(dp[j,dp[j-ww[i]]]+v[i]);
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值