背包问题学习笔记-混合背包问题

题意描述:

bash 复制代码
有 N 种物品和一个容量是 V 的背包。

物品一共有三类:

第一类物品只能用1次(01背包);
第二类物品可以用无限次(完全背包);
第三类物品最多只能用 si 次(多重背包);每种体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。

输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。

接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。

si=−1 表示第 i 种物品只能用1次;
si=0 表示第 i 种物品可以用无限次;
si>0 表示第 i 种物品可以使用 si 次;

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

数据范围
0<N,V≤1000
0<vi,wi≤1000
−1≤si≤1000

示例:

4 5

1 2 -1

2 4 1

3 4 0

4 5 2
8


解题思路:

Alice: 笑死🤣,这题可以直接改一下输入,改成多重背包就可以了

Bob: 对,01背包的物品数量就是 1,数量 1 也是多重背包的一种。完全背包的话,背包体积是有限的,能装进去的物品数量也是有限的,直接算一下,然后上取整就得了。其他代码不用改,直接用单调队列优化的肯定能过。


代码:

转换成多重背包 + 单调队列求解

js 复制代码
const fs = require('fs');
let buffer = '';

process.stdin.on('readable', () => {
    const chunk = process.stdin.read();
    if (chunk) {
        buffer += chunk.toString()
    }
});

// 输入的字符串转换为数字
const convert = (inputString) => {
    const list = [];
    inputString.split('\n').forEach((line) => {
        const tokens = line.split(' ');
        list.push(tokens.map(num => parseInt(num, 10)));
    });
    return list;
}

// 批量调用
const batchCall = (list, solve) => {
    // 划分数据
    const data = [];
    let countAndVolumIndex = 0;
    while(countAndVolumIndex < list.length) {
        const [count, volum] = list[countAndVolumIndex];
        // 转化成多重背包问题
        data.push({
            volum: volum,
            count: count,
            volumAndWeightAndCount: list.slice(countAndVolumIndex + 1, countAndVolumIndex + 1 + count).map(item => {
                const [v, w, s] = item;
                let normalizedS = s;
                if(s === -1) {
                    normalizedS = 1;
                }
                if(s === 0) {
                    normalizedS = Math.ceil(volum / v);
                }
                return [v, w, normalizedS];
            }),
        });
        countAndVolumIndex += count + 1;
    }
    
    data.forEach(item => {
        if(solve && item && item.count && item.volum) {
            solve(item.count, item.volum, item.volumAndWeightAndCount);
        }
    });
}


const solve = (count, maxVolum, volumAndWeightAndCount) => {
    // 单调队列优化方法
    const dp = new Array(maxVolum + 10).fill(0);
    // 对于每种物品 
    for (let i=0; i<count; ++i) {
        // 状态压缩
        const lastRowDp = [...dp];
        // 取出第 i 种物品的体积,价值,数量
        const [vi, wi, si] = volumAndWeightAndCount[i];
        // 对于每种可能剩余的体积,0,1,2, ... vi-1 
        for (let r=0; r<vi; ++r) {
            // 单调队列求解每种可能的最大值,滑动窗口大小是,math.min (si, maxVolum / vi) 下取整
            // 0 + 0v, 0 + 1v, 0 + 2v ... 0 + kv 的数组中滑动,每次一步
            // 最大价值对应的体积的单调队列,双端队列,queue[0] 是合法的最大价值对应的体积
            const queue = [];
            for(let j=r; j<=maxVolum; j+=vi) {
               // 维护队首
               // i 物品的体积超了
               while(queue.length && j-queue[0] > vi*si) {
                   queue.shift();
               }
               // 维护队尾
               while(queue.length && lastRowDp[queue[queue.length-1]] + (j - queue[queue.length-1]) /vi * wi <= lastRowDp[j]) {
                   queue.pop();
               }
               // 入队
               queue.push(j);
               // 更新 dp
               dp[j] = lastRowDp[queue[0]] + (j-queue[0]) / vi * wi;
            }
        }
    }

    
    console.log(dp[maxVolum]);
}

process.stdin.on('end', function() {
    batchCall(convert(buffer), solve)
});

参考:

相关推荐
Martin -Tang2 小时前
Vue 3 中,ref 和 reactive的区别
前端·javascript·vue.js
FakeOccupational3 小时前
nodejs 020: React语法规则 props和state
前端·javascript·react.js
2401_858286113 小时前
L7.【LeetCode笔记】相交链表
笔记·leetcode·链表
放逐者-保持本心,方可放逐3 小时前
react 组件应用
开发语言·前端·javascript·react.js·前端框架
曹天骄4 小时前
next中服务端组件共享接口数据
前端·javascript·react.js
龙中舞王5 小时前
Unity学习笔记(2):场景绘制
笔记·学习·unity
丶Darling.5 小时前
Day40 | 动态规划 :完全背包应用 组合总和IV(类比爬楼梯)
c++·算法·动态规划·记忆化搜索·回溯
青椒大仙KI116 小时前
24/11/7 算法笔记 PCA主成分分析
笔记·算法·信息可视化
我是哈哈hh6 小时前
专题十八_动态规划_斐波那契数列模型_路径问题_算法专题详细总结
c++·算法·动态规划
郝晨妤6 小时前
鸿蒙ArkTS和TS有什么区别?
前端·javascript·typescript·鸿蒙