多重背包问题

本文包含的内容:

<1> 问题描述

<2> 基本思路(和完全背包类似)

<3> 转换为01背包问题求解(直接利用01背包)

---------------------------------------------

1、问题描述

已知:有一个容量为V的背包和N件物品,第i件物品最多有Num[i]件,每件物品的重量是weight[i],收益是cost[i]。

问题:在不超过背包容量的情况下,最多能获得多少价值或收益

举例:物品个数N = 3,背包容量为V = 8,则背包可以装下的最大价值为64.

----------------------------------------------

2、基本思路(直接扩展01背包的方程)

由于本问题和完全背包很类似,这里直接给出方程。

[cpp] view plaincopyprint?

  1. 状态转移方程:
  2. f[i][v]:表示前i件物品放入重量为v的背包获得的最大收益
  3. f[i][v] = max(f[i][v],f[i - 1][V - k * Weight[i]] + k * Value[i]);
  4. 其中0 <= k <= min(Num[i],V/Weight[i]);//这里和完全背包不同。
  5. 边界条件
  6. f[i][0] = 0;
  7. f[v][0] = 0;
状态转移方程:

f[i][v]:表示前i件物品放入重量为v的背包获得的最大收益
f[i][v] = max(f[i][v],f[i - 1][V - k * Weight[i]] + k * Value[i]);
其中0 <= k <= min(Num[i],V/Weight[i]);//这里和完全背包不同。
边界条件
f[i][0] = 0;
f[v][0] = 0;

代码:

[cpp] view plaincopyprint?

  1. #include <iostream>
  2. using namespace std;
  3. const int N = 3;//物品个数
  4. const int V = 8;//背包容量
  5. int Weight[N + 1] = {0,1,2,2};
  6. int Value[N + 1] = {0,6,10,20};
  7. int Num[N + 1] = {0,10,5,2};
  8. int f[N + 1][V + 1] = {0};
  9. /*
  10. f[i][v]:表示把前i件物品放入容量为v的背包中获得的最大收益。
  11. f[i][v] = max(f[i - 1][v],f[i - 1][v - k * Weight[i]] + K * Value[i]);其中1 <= k <= min(Num[i],V/Weight[i])
  12. //初始化
  13. f[i][0] = 0;
  14. f[0][v] = 0;
  15. */
  16. int MultiKnapsack()
  17. {
  18. int nCount = 0;
  19. //初始化
  20. for (int i = 0;i <= N;i++)
  21. {
  22. f[i][0] = 0;
  23. }
  24. for (int v = 0;v <= V;v++)
  25. {
  26. f[0][v] = 0;
  27. }
  28. //递推
  29. for (int i = 1;i <= N;i++)
  30. {
  31. for (int v = Weight[i];v <= V;v++)
  32. {
  33. f[i][v] = 0;
  34. nCount = min(Num[i],v/Weight[i]);//是当前背包容量v,而不是背包的总容量
  35. for (int k = 0;k <= nCount;k++)
  36. {
  37. f[i][v] = max(f[i][v],f[i - 1][v - k * Weight[i]] + k * Value[i]);
  38. }
  39. }
  40. }
  41. return f[N][V];
  42. }
  43. int main()
  44. {
  45. cout<<MultiKnapsack()<<endl;
  46. system("pause");
  47. return 1;
  48. }
#include <iostream>
using namespace std;
const int N = 3;//物品个数
const int V = 8;//背包容量
int Weight[N + 1] = {0,1,2,2};
int Value[N + 1] = {0,6,10,20};
int Num[N + 1] = {0,10,5,2};
int f[N + 1][V + 1] = {0};
/*
f[i][v]:表示把前i件物品放入容量为v的背包中获得的最大收益。
f[i][v] = max(f[i - 1][v],f[i - 1][v - k * Weight[i]] + K * Value[i]);其中1 <= k <= min(Num[i],V/Weight[i])
//初始化
f[i][0] = 0;
f[0][v] = 0;
*/
int MultiKnapsack()
{
	int nCount = 0;
	//初始化
	for (int i = 0;i <= N;i++)
	{
		f[i][0] = 0;
	}
	for (int v = 0;v <= V;v++)
	{
		f[0][v] = 0;
	}
	//递推
	for (int i = 1;i <= N;i++)
	{
		for (int v = Weight[i];v <= V;v++)
		{
			f[i][v] = 0;
			nCount = min(Num[i],v/Weight[i]);//是当前背包容量v,而不是背包的总容量
			for (int k = 0;k <= nCount;k++)
			{
				f[i][v] = max(f[i][v],f[i - 1][v - k * Weight[i]] + k * Value[i]);
			}
		}
	}
	return f[N][V];
}
int main()
{
	cout<<MultiKnapsack()<<endl;
	system("pause");
	return 1;
}

复杂度分析:

程序需要求解N*V个状态,每一个状态需要的时间为O(v/Weight[i]),总的复杂度为O(NV*Σ(V/Weight[i]))。

3、转换为01背包问题求解(直接利用01背包)

思路 1、直接对每一件物品进行拆分成min(Num[i],V/Weight[i])件,之后在拆分后的集合上进行01背包的求解。

时间复杂度:和基本思路一样,没有降低。

思路 2、采用二进制拆分的思想。对每i件物品,拆分的策略为:新拆分的物品的重量等于1件,2件,4件,..,(2^(k - 1)),Num[i] - (2^(k - 1))件,其中k 是满足Num[i] - 2^k + 1 > 0 的最大整数。

注意,

(1)最后一个物品的件数的求法和前面不同,其直接等于 该物品的最大件数 - 前面已经分配之和。

(2)分成的这几件物品的系数和为

Num[i],表明第i种物品取的件数不能多于Num[i]。

举例:某物品为13件,则其可以分成四件物品,其系数为1,2,4,6.这里k = 3。

当然,这里使用二进制的前提还是使用二进制拆分能

保证对于0,,,Num[i]间的每一个整数,均可以用若干个系数的和表示。

具体使用时,有一个小优化,即:

我们不对所有的物品进行拆分,因此物品一旦拆分,其物品个数肯定增加,那么复杂度肯定上去。

此时,我们可以选择性地对物品进行拆分:

(1)如果第i个物品的重量Weight[i] * 物品的个数Num[i] >= 背包总重量V,可以不用拆分。

(2)如果第i个物品的重量Weight[i] * 物品的个数Num[i] < 背包总重量V,可以不用拆分。

其实,拆不拆分,就看该物品能不能满足完全背包的条件。即,看该物品能不能无限量供应。

解释:为啥满足Weight[i] * 物品的个数Num[i] >= 背包总重量V的物品可以不用拆分?

此时,满足该条件时,此物品原则上是无限供应,直到背包放不下为止。

最终,对于不需要拆分的物品,可以看出完全背包的情况,调用处理完全背包物品的函数。对于需要拆分的物品,可以看出01背包的情况,调用处理01背包物品的函数。

这样,由于不对满足完全背包的物品进行拆分,此时物品个数就没有对所有物品拆分时的物品个数多,即程序中外层循环降低,复杂度也就下去了。

伪代码:

这里:C表示该物品的重量。M表示该物品的个数。V表示背包的最大容量。W表示该物品的收益。

代码:

[cpp] view plaincopyprint?

  1. #include <iostream>
  2. using namespace std;
  3. const int N = 3;//物品个数
  4. const int V = 8;//背包容量
  5. int Weight[N + 1] = {0,1,2,2};
  6. int Value[N + 1] = {0,6,10,20};
  7. int Num[N + 1] = {0,10,5,2};
  8. int f[V + 1] = {0};
  9. /*
  10. f[v]:表示把前i件物品放入容量为v的背包中获得的最大收益。
  11. f[v] = max(f[v],f[v - Weight[i]] + Value[i]);
  12. v的为逆序
  13. */
  14. void ZeroOnePack(int nWeight,int nValue)
  15. {
  16. for (int v = V;v >= nWeight;v--)
  17. {
  18. f[v] = max(f[v],f[v - nWeight] + nValue);
  19. }
  20. }
  21. /*
  22. f[v]:表示把前i件物品放入容量为v的背包中获得的最大收益。
  23. f[v] = max(f[v],f[v - Weight[i]] + Value[i]);
  24. v的为增序
  25. */
  26. void CompletePack(int nWeight,int nValue)
  27. {
  28. for (int v = nWeight;v <= V;v++)
  29. {
  30. f[v] = max(f[v],f[v - nWeight] + nValue);
  31. }
  32. }
  33. int MultiKnapsack()
  34. {
  35. int k = 1;
  36. int nCount = 0;
  37. for (int i = 1;i <= N;i++)
  38. {
  39. if (Weight[i] * Num[i] >= V)
  40. {
  41. //完全背包:该类物品原则上是无限供应,
  42. //此时满足条件Weight[i] * Num[i] >= V时,
  43. //表示无限量供应,直到背包放不下为止.
  44. CompletePack(Weight[i],Value[i]);
  45. }
  46. else
  47. {
  48. k = 1;
  49. nCount = Num[i];
  50. while(k <= nCount)
  51. {
  52. ZeroOnePack(k * Weight[i],k * Value[i]);
  53. nCount -= k;
  54. k *= 2;
  55. }
  56. ZeroOnePack(nCount * Weight[i],nCount * Value[i]);
  57. }
  58. }
  59. return f[V];
  60. }
  61. int main()
  62. {
  63. cout<<MultiKnapsack()<<endl;
  64. system("pause");
  65. return 1;
  66. }
时间: 2024-12-08 21:41:19

多重背包问题的相关文章

O(V*n)的多重背包问题

多重背包问题: 有n件物品,第i件价值为wi,质量为vi,有c1件,问,给定容量V,求获得的最大价值. 朴素做法: 视为0,1,2,...,k种物品的分组背包 [每组只能选一个] f[i][j]=Max(f[i][j-k*v[i]]+k*w[i]) 但是i,j,k都要枚举,复杂度为 n*V*k 朴素做法的改进: 因为发现用二进制可以表示1..k之内的所有数 [整数二进制打开后为01串,所以可以被二进制表示] 所以将k个物品拆分成1,2,4...2^m,k-2^m   ( 其中2^m<=k<2^

多重背包问题(来源:背包九讲)

问题: 有N种物品和一个容量为V的背包.第i种物品最多有n[i]件可用,每件费用是c[i],价值是w[i].求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大. 基本算法: 这题目和全然背包问题非常类似.主要的方程仅仅需将全然背包问题的方程稍微一改就可以,由于对于第i种物品有n[i]+1种策略:取0件,取1件--取n[i]件.令f[i][v]表示前i种物品恰放入一个容量为v的背包的最大权值,则有状态转移方程:f[i][v]=max{f[i-1][v-k*c[i]]+k*w

【动态规划/多重背包问题】POJ1014-Dividing

多重背包问题的优化版来做,详见之前的动态规划读书笔记. dp[i][j]表示前i中数加得到j时第i种数最多剩余几个(不能加和得到i的情况下为-1)递推式为: dp[i][j]=mi(dp[i-1][j]≥0,即前i-1种数就能达到数字j) =-1(j<ai 或者 dp[i][j-ai]≤0,即再加上一个第i种数也无法达到j 或者 当前和小于当前数) =dp[i][j-ai]-1(可以达到的情况) #include<iostream> #include<cstdio> #inc

C语言-多重背包问题

多重背包问题 问题:有N种物品和一个容量为V的背包.第i种物品最多有n[i]件可用,每件费用是c[i],价值是w[i].求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大. 分析: 这题目和完全背包问题很类似.基本的方程只需将完全背包问题的方程略微一改即可,因为对于第i种物品有n[i]+1种策略:取0件,取1件--取n[i]件.令f[i][v]表示前i种物品恰放入一个容量为v的背包的最大权值,则有状态转移方程: f[i][v]=max{f[i-1][v-k*c[i]]+k

动态规划-多重背包问题

0-1背包问题 完全背包问题 多重背包问题是0-1背包问题和完全背包问题的综合体,可以描述如下:从n种物品向容积为V的背包装入,其中每种物品的体积为w,价值为v,数量为k,问装入的最大价值总和? 我们知道0-1背包问题是背包问题的基础,所以在解决多重背包问题的时候,要将多重背包向0-1背包上进行转换.在多重背包问题中,每种物品有k个,可以将每种物品看作k种,这样就可以使用0-1背包的算法.但是,这样会增加数据的规模.因为该算法的时间复杂度为O(V*∑ni=1ki),所以要降低每种物品的数量ki.

HDU ACM 2191 悼念512汶川大地震遇难同胞——珍惜现在,感恩生活-&gt;多重背包问题

分析:多重背包问题.这里直接转换为01背包,为使用二进制. #include<iostream> using namespace std; int price[101]; int weight[101]; int number[101]; int dp[101]; int main() { int C,n,m; int i,j,k; cin>>C; while(C--) { cin>>n>>m; for(i=0;i<m;i++) { cin>&g

动态规划第二讲——完全背包与多重背包问题

上一节,我们讨论了01背包问题,说明了*递归与分治法 与 动态规划DP的区别和联系,介绍了缓存的概念*.以下,我们用DC.DP.cache分别表示分治法.动态规划和缓存.本节,我们讨论01背包的另外两种形似-- 完全背包和多重背包问题,分析DP问题的另外一些情况. 例一:完全背包问题 同样有n种价值和重量分别为weight[i] and value[i], 背包大小W.限制条件:每种物品数目是无限的.问:能挑选出来的总价值最大的物品组合总重量? 分析一:这里,我们无视数量有限这个条件,可以得到下

多重背包问题II

多重背包问题II 总体积是m,每个小物品的体积是A[i] ,每个小物品的数量是B[i],每个小物品的价值是C[i] 求能够放入背包内的最大物品能够获得的最大价值 和上一个很类似 上一题体积就是价值,这里的价值是单独定义了 状态转移方程 不放A[i] f[i][j] =f[i-1][j] 放A[j] 可放多个设为k, k = min(j/A[i],B[i]) f[i][j] = f[i-1][j- ki*A[i]] + ki*C[i] 0<=ki<=k 取最大值 完全背包问题时候:0<=k

poj 1742 多重背包问题 dp算法

题意:硬币分别有 A1.....An种,每种各有C1......Cn个,问组成小于m的有多少种 思路:多重背包问题 dp[i][j]表示用前i种硬币组成j最多剩下多少个  dp=-1的表示凑不齐 dp[0][0]=0; for(int i=0;i<n;i++)     for(int j=0;j<=m;j++) {    if(dp[i][j]>=0)   dp[i+1][j]=c[i];  //表示用前i种可以凑齐j元,自然就全部剩下了       else if(j<a[i]|

【动态规划】多重背包问题

说明 前面已经介绍完了01背包和完全背包,今天介绍最后一种背包问题--多重背包. 这个背包,听起来就很麻烦的样子.别慌,只要你理解了前面的两种背包问题,拿下多重背包简直小菜一碟. 如果没有看过前两篇01背包和完全背包的文章,强烈建议先阅读一下,因为本文跟前两篇文章关联性很强. 多重背包 有N种物品和一个容量为T的背包,第i种物品最多有M[i]件可用,价值为P[i],体积为V[i],求解:选哪些物品放入背包,可以使得这些物品的价值最大,并且体积总和不超过背包容量. 对比一下完全背包,其实只是多了一