当前位置:首页 > C++知识 > 正文内容

混合背包

亿万年的星光2周前 (10-18)C++知识7

1.问题定义:

混合背包问题是经典背包问题的一个变种,其中物品的类型不单一,而是混合了以下三种类型:

  1. 01 背包物品:每种物品最多只能选一次。

  2. 完全背包物品:每种物品可以选择无限次。

  3. 多重背包物品:每种物品有指定的最大数量 s_i。

目标是在背包容量为 V 的情况下,选择物品放入背包,使得总价值最大。

2.问题形式化:

假设有 N 种物品和一个容量为 V 的背包。第 i 件物品的体积是 v_i,价值是 w_i,数量是 s_i:

  • 如果 s_i = -1:表示这是 01 背包物品(只能选 0 或 1 次)

  • 如果 s_i = 0:表示这是完全背包物品(可选无限次)

  • 如果 s_i > 0:表示这是多重背包物品(最多选 s_i 次)

3.基本思路:

我们可以根据物品的类型,分别用不同的状态转移方法处理。

动态规划状态定义: 设 dp[j] 表示容量为 j 的背包能装的最大价值。

三种情况的处理:

  1. 01 背包(s_i = -1) 状态转移:逆序枚举容量,防止重复选择 dp[j] = max(dp[j], dp[j - v_i] + w_i),其中 j 从 V 到 v_i

  2. 完全背包(s_i = 0) 状态转移:正序枚举容量,允许重复选择 dp[j] = max(dp[j], dp[j - v_i] + w_i),其中 j 从 v_i 到 V

  3. 多重背包(s_i > 0) 可以用二进制优化转化为 01 背包问题,然后按 01 背包处理(逆序枚举)

二进制优化:将数量 s_i 拆分成 1, 2, 4, ..., 2^k, c(其中 c = s_i - (2^{k+1} - 1) 且 c > 0),这样这些系数的组合可以表示 0 到 s_i 之间的任意数量。每个拆分后的物品视为一个独立的 01 背包物品。

4.算法步骤:

  1. 读入物品数据 (v_i, w_i, s_i)

  2. 对每个物品:

    • 如果是 01 背包:直接按 01 背包处理(逆序容量循环)

    • 如果是完全背包:直接按完全背包处理(正序容量循环)

    • 如果是多重背包:先二进制拆分,再把拆分后的每个物品按 01 背包处理

  3. 输出 dp[V]

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1000;  // 最大物品数
const int MAXV = 1000;  // 最大背包容量
struct Good {
	int kind; // 0:01背包, 1:完全背包
	int v, w;
};
Good goods[MAXN * 20];  // 存储所有物品(考虑二进制拆分)
int dp[MAXV + 5];       // DP数组
int goods_cnt = 0;      // 物品计数器
int main() {
	int N, V;
	cin >> N >> V;
// 读入并预处理物品
	for (int i = 0; i < N; i++) {
		int v, w, s;
		cin >> v >> w >> s;
		if (s == -1) {
			// 01背包物品
			goods[goods_cnt].kind = 0;
			goods[goods_cnt].v = v;
			goods[goods_cnt].w = w;
			goods_cnt++;
		} else if (s == 0) {
			// 完全背包物品
			goods[goods_cnt].kind = 1;
			goods[goods_cnt].v = v;
			goods[goods_cnt].w = w;
			goods_cnt++;
		} else {
			// 多重背包物品 - 二进制拆分
			int k = 1;
			while (k <= s) {
				goods[goods_cnt].kind = 0;
				goods[goods_cnt].v = v * k;
				goods[goods_cnt].w = w * k;
				goods_cnt++;
				s -= k;
				k *= 2;
			}
			if (s > 0) {
				goods[goods_cnt].kind = 0;
				goods[goods_cnt].v = v * s;
				goods[goods_cnt].w = w * s;
				goods_cnt++;
			}
		}
	}
// 初始化DP数组
	for (int j = 0; j <= V; j++) {
		dp[j] = 0;
	}
// DP过程
	for (int i = 0; i < goods_cnt; i++) {
		int kind = goods[i].kind;
		int v = goods[i].v;
		int w = goods[i].w;
		if (kind == 0) {
			// 01背包或拆分后的多重背包物品
			for (int j = V; j >= v; j--) {
				if (dp[j] < dp[j - v] + w) {
					dp[j] = dp[j - v] + w;
				}
			}
		} else {
			// 完全背包物品
			for (int j = v; j <= V; j++) {
				if (dp[j] < dp[j - v] + w) {
					dp[j] = dp[j - v] + w;
				}
			}
		}
	}
	cout << dp[V] << endl;
	return 0;
}



代码说明:


  1. 数组定义:

    • goods数组:存储所有物品,包括原始物品和二进制拆分后的物品

    • dp数组:动态规划状态数组

  2. 物品预处理:

    • 01背包和完全背包物品直接存入goods数组

    • 多重背包物品通过二进制拆分,将每个拆分后的物品视为01背包物品存入

  3. DP过程:

    • 遍历所有物品

    • 对于01背包类型,从大到小遍历容量(防止重复选择)

    • 对于完全背包类型,从小到大遍历容量(允许重复选择)

  4. 二进制拆分原理:

    • 将数量s拆分为1, 2, 4, ..., 2^k, c(剩余部分)

    • 这些数的组合可以表示0到s之间的任意整数

    • 例如:s=10,拆分为1,2,4,3


扫描二维码推送至手机访问。

版权声明:本文由青少年编程知识记录发布,如需转载请注明出处。

分享给朋友:
返回列表

上一篇:多重背包问题

没有最新的文章了...

相关文章

如何判断回文数/回文串

所谓回文,就是从左往右读和从右往左读都是一样的,这样的数字或者字符称为回文数/回文字符。做题的时候经常能看到判断回文操作。判断回文的一般有两种,一种是数字类型,一种是字符类型。两种分别介绍一下。一、回...

【题解】奶牛的回音

【题目描述】奶牛们灰常享受在牛栏中牟叫,因為她们可以听到她们牟声的回音。虽然有时候并不能完全听到完整的回音。Bessie曾经是一个出色的秘书,所以她精确地纪录了所有的牟叫声及其回声。她很好奇到底两个声...

C++中箭头指针的含义及用法

C++中箭头指针的含义及用法

0.前言c++中我们在一些程序中看到箭头 p—>stu 类似于这样的表示。今天就简单来解释一下点运算和箭头运算。1.点运算常见的点一般出现在结构体中,比如下面的代码:#include<io...

深搜剪枝技巧

一、什么是剪枝     首先应当明确的是,“剪枝”的含义是什么。我们知道,搜索的进程可以看作是从树根出发,遍历一棵倒置的树——搜索树的过程。而所谓剪枝,顾名思义...

【数据结构】栈的基本操作

0.前言上一篇中简单介绍了栈的定义,这一篇中介绍栈的基本用法,包含压栈,出栈,判断栈空,判断栈中元素个数等。下面进行详细介绍1.基本用法本文介绍的栈的主要操作,使用栈之前加入<stack>...

指针(一):基础用法

1.定义什么是指针,简单来说:“指针就是地址”。2.指针变量的定义指针变量定义形式:  类型说明符  *变量名其中,*号表示指针变量。变量名即为定义的指针变量名,类型说明符表示该指...