背包问题(多重背包问题 二进制优化)

 

目录

题目

输入格式

输出格式

数据范围

输入样例

输出样例:

暴力方法如下(当然是数据较弱的时候)

二进制优化


题目

有 N种物品和一个容量是 V的背包。

第 i 种物品最多有 si件,每件体积是 vi,价值是 wi

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

输入格式

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

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

输出格式

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

数据范围

0<N≤1000
0<V≤2000
0<vi,wi,si≤2000

输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出样例:
10

 

 这一题多重背包问题和前面的01背包,完全背包问题有所不同,运用到了更好的思维方法如果数据比较弱的,直接暴力就可以过,但是如果数据较大肯定就是过不了的。

暴力方法如下(当然是数据较弱的时候

 我们可以讲si个物品都罗列出来,在用01背包的方法来写,就是在原来的基础上再加上一层循环,也不难理解

#include <bits/stdc++.h>
using namespace std;
int v[1050],w[1050],dp[1050],x[1050];
int main ()
{
	int n,m;cin>>n>>m;
	for (int i=0;i<n;i++)
	{
		cin>>v[i]>>w[i]>>x[i];
		
	}
	for (int i=0;i<n;i++)
	{
		for (int k=0;k<x[i];k++)
		for (int j=m;j>=v[i];j--)
		dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
		
	}
	cout<<dp[m];
}

这样是不是就将多重背包完美解决了呢?答案是否定的。这样枚举的方法用到的是朴素方法。怎么理解这个朴素方法呢?我们这样写是不是把有多少个一样的物品罗列多少个物品。当我们每一种物品都有2000个,有1000个,每个的体积又是2000。这样的数据是不是特别大,时间复杂度为一般的方法绝对过不了。

二进制优化

二进制优化有些类似于压缩的思想。一个物品有很多,我们有很多很多的选择方法,可以选1..2..3...n个。那么我们就需要一种很快的选择方法,而不是朴素的暴力筛选。我们二进制方法就是这样一种很好的方法。


例如,我们有10个硬币,我们怎么样快速的找出来7个?一般应该是一个一个选出来把。但是聪明的小杰(就是我)可以事先把硬币分成四堆,分别是1,2,4,3个硬币,我们只需要选择1+2+4就能选出来7个硬币。这种方法在数据比较大的时候优势尤为明显,就是很快。这种把一个数字分成一堆数字再相加的方法和我们所学的二进制方法非常相似,所以叫做二进制优化法。


因此如果要讨论10个一样的物品就转化成讨论4个不同的物品了。而n个一样的物品就转化成了log2n个不同的物品进行讨论

如果不明白的话,大家可以去看看这位老师讲的课:多重背包的二进制优化

下面是二进制优化的主要步骤

vi wi si
v[] w[]//定义的一些变量
int k=0;
int t=1;
while (si>=t)
{
    k++;//k最后表示的是总共有多少的物品
    v[k]=vi*t;
    w[i]=wi*t;
    wi-=t;t*=2;
}
if (si>0)
{
    k++;
    v[k]=si*vi;
    w[i]=vi*wi;
}

下面看下我的代码

#include <bits/stdc++.h>
#define int long long 
using namespace std;
const int N = 1e5;
int v[N],w[N],dp[N];

signed main ()
{
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int n,m;cin>>n>>m;
	int k=0;
	for (int i=1;i<=n;i++)
	{
		int vi,wi,si;
		cin>>vi>>wi>>si;
		int t=1;
		while (t<=si)
		{
			k++;
			v[k]=vi*t;
			w[k]=wi*t;
			si-=t;  
			t*=2;
		}
		if (si>0)
		{
			k++;
			v[k]=si*vi;w[k]=si*wi;
		}
		
	}//这里就是相当于预处理一下这些物品
    //前面的一旦处理好,后面的内容就和01背包一样了
	for (int i=1;i<=k;i++)//注意这里不是i<=n而是i<=k
	{
		for (int j=m;j>=v[i];j--)
		dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
	}
	cout<<dp[m]<<'\n';
	return 0;
}

<think>嗯,用户问的是用bitset来优化01背包问题,这个我得仔细想想怎么解释清楚。首先,01背包的基本动态规划方法是用一个数组dp[i][j]表示前i个物品,容量为j时的最大价值。然后空间优化成一维数组,每次逆序更新。但是当背包容量很大时,比如1e5以上,常规的O(nV)时间复杂度可能无法承受,这时候bitset就有用了。那bitset是怎么优化的呢?应该和状态压缩有关。常规方法中,每个状态是布尔值,表示是否能达到某个容量。这时候可以把布尔数组替换成bitset,利用位运算的并行处理来加速。比如,初始时bitset的第0位是1,表示容量0可以达到。当处理一个物品时,原来的状态左移w位,相当于所有可能的容量加上w,然后用或操作合并到原来的状态中。这样每次处理一个物品的时间复杂度是O(V/word_size),比如64位系统的话就是O(V/64),大大减少了时间。 不过需要注意的是,这种方法只能处理是否恰好达到某个容量的情况,而传统的01背包是求最大价值。所以可能用户的问题是在问是否存在恰好达到某个容量的情况,或者如果是求最大值的话,可能需要其他处理方式。不过通常bitset优化适用于存在性问题,比如能否装满背包,或者求某些特定条件下的可行性。 然后需要给出具体的代码示例。比如,初始化一个bitset,然后遍历每个物品,用bitset |= bitset<< w。最后检查目标容量是否可达。不过如果是求最大价值的话,可能需要每个位存储的是价值,但这样的话bitset可能不太适用,因为每个位只能表示0或1。所以可能用户的问题更偏向于存在性判断,而不是求最大价值。 另外,需要解释为什么这样优化有效。主要是因为bitset的位操作是并行的,每次处理多个位,减少了循环次数。比如,常规的循环每个容量都要单独处理,而bitset可以一次处理多个容量,提高了效率。在代码部分,应该展示如何用bitset来实现。例如,定义一个足够大的bitset,比如bitset<100001> dp,然后初始设置dp[0]= 1。对于每个物品的重量w,执行dp|= dp << w。这样处理后,dp中所有为1的位表示可以达到的容量。最后检查目标容量对应的位是否为1。 可能用户还希望知道这种方法的适用场景和限制。比如,只能处理存在性问题,不能处理价值最大化的问题,或者当容量极大时,比如1e5以上,bitset的大小是否可行。比如C++的bitset需要编译时确定大小,所以如果容量很大,可能需要使用动态的bitset(比如vector<bool>),但vector<bool>的效率可能不如bitset,因为bitset是栈上分配的,而vector<bool>是堆上的,且可能有位压缩的开销。 另外,可能的错误点包括忘记初始化dp[0]为1,或者在处理多个物品时没有正确更新bitset。需要确保每次处理物品时都正确地进行或运算和位移操作。 最后,相关问题可能涉及bitset的其他应用,动态规划的其他优化方法,或者如何处理存在性以外的背包问题变种。例如,询问如何用bitset处理完全背包问题,或者如何在存在性判断之外记录选择的物品,或者如何处理体积较大的情况下的优化方法。</think>使用 `bitset` 优化 01 背包的核心思路是通过位运算的并行性加速状态转移,将时间复杂度从 `O(nV)` 优化到 `O(nV/32)`(或 `O(nV/64)`,取决于机器位数)。以下是详细实现和解释: --- ### **实现代码** ```cpp #include <bitset> #include <iostream> using namespace std; const int MAX_V = 1e5 + 10; // 最大背包容量 bitset<MAX_V> dp; // 存储可达容量状态 void zero_one_knapsack_bitset(const int weights[], int n, int V) { dp.reset(); // 初始化所有位为 0 dp.set(0); // 初始状态:容量 0 可达 for (int i = 0; i < n; ++i) { int w = weights[i]; dp |= dp << w; // 关键:位运算并行更新状态 } // 输出结果 for (int v = V; v >= 0; --v) { if (dp.test(v)) { cout << "可达最大容量: " << v << endl; break; } } } // 示例调用 int main() { int weights[] = {3, 4, 2}; int V = 10; // 背包容量 int n = sizeof(weights) / sizeof(weights[0]); zero_one_knapsack_bitset(weights, n, V); return 0; } ``` --- ### **原理解释** 1. **状态表示** `bitset` 的每一位 `dp[v]` 表示容量 `v` 是否可达(`true`/`false`)。例如,`dp[5] = 1` 表示存在一种物品组合使得总容量恰好为 5。 2. **状态转移** 对于每个物品的重量 `w`,原始状态 `dp` 可以通过 `dp << w` 左移 `w` 位,得到新的可达状态(相当于将当前所有可达容量加上 `w`)。 通过 `dp |= dp << w`,将新旧状态合并,表示选择或不选择当前物品的所有可能性。 3. **位运算优势** - 每个 `bitset` 操作(如移位、按位或)在硬件层面是并行的,一次处理 32/64 位(取决于机器)。 - 例如,若 `MAX_V = 1e5`,传统循环需要 1e5 次操作,而 `bitset` 仅需 `1e5/32 ≈ 3125` 次操作。 4. **适用场景** 该优化适用于以下情况: - 仅需判断某个容量是否可达(存在性问题)。 - 物品数量较多(`n` 较大),但背包容量 `V` 在可接受范围内(例如 `V ≤ 1e5`)。 --- ### **限制与注意事项** 1. **编译时确定容量** `bitset` 的大小(如 `MAX_V`)必须在编译时确定,若容量动态变化需改用 `vector<bool>`(性能略低)。 2. **无法直接记录价值** 此方法仅适用于判定可达性,若需记录最大价值,仍需传统动态规划。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值