再来一边背包问题 | 完全背包问题

哈喽哈喽这里是小菜不拖延博主,本篇是背包问题的第二篇,主要基于acwing再重温一边完全背包问题,希望能够帮到你~

完全背包问题

完全背包问题在01背包(再来一遍背包问题 | 01背包)的基础上不同的是,每个物品可以被选多次

原题链接:3. 完全背包问题 - AcWing题库

基本逻辑代码

也就是每个物品可以被选k次,如果理解了01背包的话,其实这里也就是又要多一层循环,每个物品被选k次 (基本逻辑现在是会超时的)

c++ 复制代码
#include<iostream>
#include<algorithm>

using namespace std;

const int N=1010;

int n,m;
int v[N], w[N];
int f[N][N];
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        cin>>v[i]>>w[i];
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<=m;j++)
        {
           //每次的k件总体积要小于当前j体积才能装下
            for(int k=0;k*v[i]<=j;k++){
             f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
            }

            
        }
    }
    cout<<f[n][m]<<endl;
    return 0;
}
js 复制代码
function knapsack(n, m, items) {
    let f = Array(n+1).fill().map(() => Array(m+1).fill(0));
    for (let i = 1; i <= n; i++) {
        for (let j = 0; j<=m; j++) {
            for(let k=0;k*v[i]<=j;k++){
            f[i][j] = Math.max(f[i][j], f[i-1][j - items[i][0]] + items[i][1]);
            }
        }
    }
    
    return f[n-1][m];
}

// 示例输入
let n = 4;
let m = 3;
let items = [
    [0,0],
    [1, 2],  // 物品1: 重量 1,价值 2
    [2, 4],  // 物品2: 重量 2,价值 4
    [2, 5],  // 物品3: 重量 3,价值 4
    [4, 5]   // 物品4: 重量 4,价值 5
];

console.log(knapsack(n, m, items));  // 输出最大价值

疑难解释

为什么不再像01背包一样判断是否需要取当前物品了

因为我们加入了参数k,当k等于0的时候就相当于取前i-1个物品,而且我们已经在循环条件中加入了体积判断,就不需要再进行一次判断了,相当于这里进行了小小的优化

k循环优化

c++ 复制代码
#include<iostream>
#include<algorithm>

using namespace std;

const int N=1010;

int n,m;
int v[N], w[N];
int f[N][N];
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        cin>>v[i]>>w[i];
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<=m;j++)
        {
            f[i][j]=f[i-1][j];
            
            if(j>=v[i]) f[i][j]=max(f[i][j],f[i][j-v[i]]+w[i]);
    

            
        }
    }
    cout<<f[n][m]<<endl;
    return 0;
}
js 复制代码
function knapsack(n, m, items) {
    let f = Array(n+1).fill().map(() => Array(m+1).fill(0));
    for (let i = 1; i <= n; i++) {
        for (let j = 0; j<=m; j++) {
        //不选第i个物品
            f[i][j]=f[i-1][j]
            //选第i个物品,注意这里代码和01不一样
            if(j>=items[i][0]) f[i][j] = Math.max(f[i-1][j], f[i][j - items[i][0]] + items[i][1]);
        }
    }
    
    return f[n-1][m];
}

// 示例输入
let n = 4;
let m = 5;
let items = [
    [0,0],
    [1, 2],  // 物品1: 重量 1,价值 2
    [2, 4],  // 物品2: 重量 2,价值 4
    [3, 4],  // 物品3: 重量 3,价值 4
    [4, 5]   // 物品4: 重量 4,价值 5
];

console.log(knapsack(n, m, items));  // 输出最大价值

注意

和01背包代码不一样!!!,看过01背包的观众老爷会发现,欸,这代码不就是01背包吗。事实上确实相似99.99%,但是恶魔往往藏在细节之中

  • 01背包:f[i][j] = Math.max(f[i-1][j], f[i-1][j - items[i][0]] + items[i][1]);
  • 完全背包:f[i][j] = Math.max(f[i-1][j], f[i][j - items[i][0]] + items[i][1]);

可以看到一个是i-1一个是i。在01背包文章中我们特意强调过这个下标问题,如果是i,那就是基于i,就等于i已经被选过了,恰巧,我们完全背包的情况还真就是i个物品能被选多次(巧了吗这不),所以上一节容易误导的地方恰巧就是完全背包的解法,等于你掌握了01背包就掌握了完全背包!!

解释

如下图,y总写的式子。

  • 第一个式子:当k=1,2,3···时,f[i][j]在选k个i物品的最大值

  • 第二个式子:当k=1,2,3···时,f[i][j-v]在选k个i物品的最大值(也就是把第一个式子k=1时提出来看他的最大值从何而来)

  • 我们会发现每次计算好像都有重复的部分,上下两个式子就差一个w,max(a+w,b+w)==max(a,b)+w,所以第二个式子两边同事加上w,第一二个式子右边黄色部分就相同了,所以替换成第二个式子的左边部分,我们就得到了最终优化后的式子 所以就等于我们不用每次都循环k,只要比上一次多一个w就好了

二维优化成一维

既然01背包能优化到一维,完全背包也一样(经过上面的对比,你是否猜到了代码呢!) 显然完全背包的下标都是i,那我们就可以轻松的直接化成一维了,不用像01背包那样因为下标不同产生额外的变化

c++ 复制代码
#include<iostream>
#include<algorithm>
 
using namespace std;
 
const int N=1010;
 
int n,m;
int v[N], w[N];
int f[N];
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        cin>>v[i]>>w[i];
    }
    for(int i=1;i<=n;i++)
    {
    //小于v[i]装不下第i个物品,直接过
        for(int j=v[i];j<=m;j++)
        {
           f[j]=max(f[j],f[j-v[i]]+w[i]);
        }
        
    }
    cout<<f[m]<<endl;
    return 0;
}
js 复制代码
function knapsack(n, m, items) {
    let f = Array(m + 1).fill(0);
    for (let i = 1; i <= n; i++) {
        let [weight, value] = items[i];
        for (let j = items[i][0]; j >=weight; j--) {
            f[j] = Math.max(f[j], f[j - weight] + value);
            console.log(f,j,weight)
        }
    }

    return f[m];
}

// 示例输入
let n = 4;
let m = 5;
let items = [
    [0,0],
    [1, 2],  // 物品1: 重量 1,价值 2
    [2, 4],  // 物品2: 重量 2,价值 4
    [3, 4],  // 物品3: 重量 3,价值 4
    [4, 5]   // 物品4: 重量 4,价值 5
];

console.log(knapsack(n, m, items));  // 输出最大价值

练习题目

相关推荐
愚润求学27 分钟前
【递归、搜索与回溯】FloodFill算法(一)
c++·算法·leetcode
sunny-ll2 小时前
【C++】详解vector二维数组的全部操作(超细图例解析!!!)
c语言·开发语言·c++·算法·面试
嵌入式@秋刀鱼3 小时前
《第四章-筋骨淬炼》 C++修炼生涯笔记(基础篇)数组与函数
开发语言·数据结构·c++·笔记·算法·链表·visual studio code
嵌入式@秋刀鱼3 小时前
《第五章-心法进阶》 C++修炼生涯笔记(基础篇)指针与结构体⭐⭐⭐⭐⭐
c语言·开发语言·数据结构·c++·笔记·算法·visual studio code
简简单单做算法3 小时前
基于PSO粒子群优化的VMD-LSTM时间序列预测算法matlab仿真
算法·matlab·lstm·时间序列预测·pso·vmd-lstm·pso-vmd-lstm
无聊的小坏坏3 小时前
高精度算法详解:从原理到加减乘除的完整实现
算法
愚润求学3 小时前
【递归、搜索与回溯】FloodFill算法(二)
c++·算法·leetcode
泽02023 小时前
C++之list的自我实现
开发语言·数据结构·c++·算法·list
南枝异客4 小时前
四数之和-力扣
java·算法·leetcode
凌肖战4 小时前
C语言中提供的第三方库之哈希表实现
算法·哈希算法