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

题意描述:

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

参考:

相关推荐
不听话坏1 小时前
Ignition篇(下 一) 动态执行前的事情
开发语言·前端·javascript
小二·2 小时前
React 19 + Next.js 15 现代前端开发实战:App Router / Server Components / 流式渲染
前端·javascript·react.js
取地址符3 小时前
C++学习笔记(基于learn-cxx)(1)
c++·经验分享·笔记·学习
ysa0510304 小时前
【板子】ST表
c++·笔记·算法·板子
CHNE_TAO_EMSM4 小时前
Android studio 打开文件时自动下载源码
前端·javascript·android studio
risc1234565 小时前
肌肉收缩会释放大量的肌细胞因子能抑制体内炎症因子
笔记
竹林8186 小时前
从 ethers.js 迁移到 Viem:一个签名验证 Bug 让我彻底放弃旧爱
javascript
既然如此,那就开摆7 小时前
精读笔记06_CoCa
笔记
你挚爱的强哥7 小时前
Vue2 实现 1.5s 十连击监听(连续点击若干次),封装通用可复用点击检测工具,不用 data!Vue 封装通用连击监听方法,支持自定义时长与点击次数
前端·javascript·vue.js
半个落月7 小时前
用 LangChain.js 手写一个能读写文件和执行命令的 Mini Cursor
javascript·人工智能·后端