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

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

练习题目

相关推荐
仟濹12 分钟前
【算法 C/C++】二维前缀和
c语言·c++·算法
Chenyu_31017 分钟前
04.基于C++实现多线程TCP服务器与客户端通信
linux·服务器·网络·c++·tcp/ip·算法·visualstudio
Dante7982 小时前
【数据结构】二叉搜索树、平衡搜索树、红黑树
数据结构·c++·算法
驼驼学编程2 小时前
决策树,Laplace 剪枝与感知机
算法·决策树·剪枝
坚强小葵2 小时前
实验8-2-1 找最小的字符串
c语言·算法
apcipot_rain2 小时前
【密码学——基础理论与应用】李子臣编著 第三章 分组密码 课后习题
python·算法·密码学
lucky1_1star3 小时前
FX-函数重载、重写(覆盖)、隐藏
java·c++·算法
KuaCpp4 小时前
蓝桥杯15届省C
算法·蓝桥杯
奔跑的废柴6 小时前
LeetCode 738. 单调递增的数字 java题解
java·算法·leetcode
武乐乐~6 小时前
欢乐力扣:合并区间
算法·leetcode·职场和发展