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

哈喽哈喽这里是小菜不拖延博主,本篇是背包问题的第二篇,主要基于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));  // 输出最大价值

练习题目

相关推荐
爱coding的橙子2 小时前
每日算法刷题 Day3 5.11:leetcode数组2道题,用时1h(有点慢)
算法·leetcode
?abc!7 小时前
缓存(5):常见 缓存数据淘汰算法/缓存清空策略
java·算法·缓存
BioRunYiXue7 小时前
一文了解氨基酸的分类、代谢和应用
人工智能·深度学习·算法·机器学习·分类·数据挖掘·代谢组学
jiunian_cn8 小时前
【c++】异常详解
java·开发语言·数据结构·c++·算法·visual studio
工藤新一¹9 小时前
蓝桥杯算法题 -蛇形矩阵(方向向量)
c++·算法·矩阵·蓝桥杯·方向向量
Levin__NLP_CV_AIGC9 小时前
解决pip安装PyPI默认源速度慢
算法·pip
Helibo449 小时前
GESPC++六级复习
java·数据结构·算法
EnticE15210 小时前
[高阶数据结构]二叉树经典面试题
数据结构·算法·面试
MarkHard12310 小时前
Leetcode (力扣)做题记录 hot100(34,215,912,121)
算法·leetcode·职场和发展
爱喝茶的小茶11 小时前
构造+简单树状
数据结构·算法