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

题意描述:

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)
});

参考:

相关推荐
JieE2121 小时前
LeetCode 101. 对称二叉树|JS 递归 + 迭代双解法,彻底搞懂镜像判断
javascript·算法
冬奇Lab3 小时前
AI Workflow 定义的四次演进:从 Markdown 到 JS 脚本,再到分布式多 Agent
javascript·人工智能·agent
一颗烂土豆9 小时前
Meshopt 压缩深度解析,为什么它比 Draco 更快
前端·javascript·webgl
kyriewen11 小时前
同事每天催我 Code Review,我写了个脚本让 AI 替我 review PR——现在他反过来催 AI 了
前端·javascript·ai编程
weedsfly14 小时前
迭代器、生成器与异步迭代——让数据“按需流动”的艺术
前端·javascript
假如让我当三天老蒯14 小时前
前端跨域解决方案(学习用)
前端·javascript·面试
铁皮饭盒16 小时前
Bun 哪比 Node.js 快?
javascript·后端
JieE2121 天前
LeetCode 56. 合并区间|超清晰 JS 图解思路,面试高频区间题
javascript·算法·面试
candyTong1 天前
RTK 技术原理:一次典型会话里,80% 上下文是怎么省下来的
javascript·后端·架构
_柳青杨1 天前
深入理解 JavaScript 事件循环
前端·javascript