【动态规划】完全背包

本文探讨了如何解决完全背包问题,通过将完全背包转化为01背包,并采用动态规划进行优化。首先介绍了基本的动态规划思路,然后通过数学推导简化了状态转移方程,减少了循环次数,最后给出了简化后的代码实现。这种方法降低了算法的时间复杂度,提高了效率。
摘要由CSDN通过智能技术生成

题目描述

设有n种物品,每种物品有一个重量及一个价值。但每种物品的数量是无限的,同时有一个背包,最大载重量为M,今从n种物品中选取若干件(同一种物品可以多次选取),使其重量的和小于等于M,而价值的和为最大。

输入格式

第一行:两个整数,M(背包容量,M<=200)和N(物品数量,N<=30);
第2~N+1行:每行二个整数Wi、Ci,表示每个物品的重量和价值。

输出格式

仅一行,一个数,表示最大总价值。

样例输入

10 4
2 1
3 3
4 5
7 9

样例输出

12

题解:

该问题与01背包不同点在于,该问题中每个物品的数量有无限个

1.转换成01背包
dp [ i ][ j ] = max(dp[ i-1 ][ j ],dp[ i - 1][ j-w[i] ] +c[ i ])

最容易想到的一个方法就是将完全背包装换成01背包。
(1)实际上,最简单的一种实现就是把每个物品复制W/w[i]份,这样把同类的多份都视作01背包中最多只能选择1次的物品,即可转换成01背包。不过这个实现空间占用较多,而且也不够优雅。

(2)第二种实现就是枚举每个物品选择的次数,要保证前i个物品在剩余 j 空间内具有最大价值,必然让每个物品选择0到W/w[i]次(W:背包空间,w[i]每个物品的体积),只要枚举物品的选择次数,并从中选择使得价值最大的次数k即可获得最大价值。

重点讲解第2种:
状态:

状态1:当前面对的物品类型 i
状态2:当前剩余的空间大小 j
dp [ i ][ j ]:面对物品 i,且空间剩余 j 时最大价值

方程:

面对第 i 种物品,可以有两种决策:
选择
选择物品 i 时,要考虑选择多少次,因此需要做一个次数的枚举,因为必然存在某个次数k使得背包价值最大,同时这个选择是在 第i - 1个物品剩余空间为 j 的基础上做出的有:

dp [ i ] [ j ] = max(dp [ i - 1 ] [ j - k * w[i] ] +k * c[ i ] , ( 0 < k ≤ W / w [ i ] ) ) (0 <k \le W/w[i])) (0<kW/w[i]))

不选择
由于不选择第 i 个物品,说明第 i 个物品不能贡献最大值,因此直接取第i-1个物品的最大结果即可

dp [ i ] [ j ] = dp [ i -1 ] [ j ]

总结
两者取大者即可

dp[ i ] [ j ] = max(dp[ i - 1 ] [ j ],dp[ i -1 ][ j - k * w[i]]+k * c[i])
初值

初值为0

#include <bits/stdc++.h>
#define maxlen 205

using namespace std;
int w[maxlen]; 
int c[maxlen]; 
int dp[35][205];
int main() {
	int m,n;
	cin>>m>>n;
	for(int i =1 ;i<=n; i++)
		cin>>w[i]>>c[i];
		
	//0.枚举每个物品 
	for(int i = 1 ;i<=n ;i++){
		for(int j = 1 ;j<=m ;j++){
			//1.不选择第i个物品 
			dp[i][j] = dp[i-1][j];
			//2.选择,且尝试枚举数量 
			for(int k = 0 ;k<=j/w[i]; k++){
				if(j>=k*w[i])
					dp[i][j] = max(dp[i][j],dp[i-1][j-k*w[i]]+k*c[i]);
			}
		}
	} 
	cout<<dp[n][m]<<endl;
   return 0;
}
2.改进

显然,上述代码的时间复杂度为 O ( n m ∗ ∑ i = 1 n W / w [ i ] ) O(nm*\sum_{i=1}^{n}W/w[i]) O(nmi=1nW/w[i]),达到 O ( n 3 ) O(n^{3}) O(n3)级别,不是一个非常好的算法。仔细分析可以发现,内层枚举数量的循环主要是在选择的情况下产生的,因此重点在于简化选择情况下的过程。
改进主要方式为简单的数学推导,如下:
由状态转移方程可知:
d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − w [ i ] ] + c [ i ] , d p [ i − 1 ] [ j − 2 ∗ w [ i ] ] + 2 ∗ c [ i ] , . . . , d p [ i − 1 ] [ j − k ∗ w [ i ] ] + k ∗ c [ i ] ) , ( 0 < k ≤ W / w [ i ] ) dp [ i ] [ j ] = max( dp[ i - 1 ] [ j ],dp[ i - 1 ] [ j - w[ i ] ] + c[ i ], dp[ i - 1 ] [ j -2*w[ i ] ]+2*c[ i ] ,..., dp[ i -1 ] [ j - k*w[i] ] +k*c[ i ]),(0< k \le W/w[i]) dp[i][j]=max(dp[i1][j],dp[i1][jw[i]]+c[i],dp[i1][j2w[i]]+2c[i],...,dp[i1][jkw[i]]+kc[i]),(0<kW/w[i])

左右两边 对空间缩小w[ i ] 并加上 c[ i ] 有:
d p [ i ] [ j − w [ i ] ] + c [ i ] = m a x ( d p [ i − 1 ] [ j − w [ i ] ] + c [ i ] , d p [ i − 1 ] [ j − 2 ∗ w [ i ] ] + 2 ∗ c [ i ] , . . . , d p [ i − 1 ] [ j − ( k + 1 ) ∗ w [ i ] ] + ( k + 1 ) ∗ c [ i ] ) , ( 0 < k ≤ W / w [ i ] ) dp [ i ] [ j - w[ i ] ] +c[ i ] = max( dp[ i - 1 ] [ j - w[ i ] ] + c[ i ], dp[ i - 1 ] [ j -2*w[ i ] ]+2*c[ i ] ,..., dp[ i -1 ] [ j - (k+1)*w[i] ] + (k+1)*c[ i ]),(0< k \le W/w[i]) dp[i][jw[i]]+c[i]=max(dp[i1][jw[i]]+c[i],dp[i1][j2w[i]]+2c[i],...,dp[i1][j(k+1)w[i]]+(k+1)c[i]),(0<kW/w[i])
由于k+1项不符合题意(每个物品最多W/w[i]项就可以填满背包),直接舍弃即可
d p [ i ] [ j − w [ i ] ] + c [ i ] = m a x ( d p [ i − 1 ] [ j − w [ i ] ] + c [ i ] , d p [ i − 1 ] [ j − 2 ∗ w [ i ] ] + 2 ∗ c [ i ] , . . . , d p [ i − 1 ] [ j − k ∗ w [ i ] ] + k ∗ c [ i ] ) , ( 0 < k ≤ W / w [ i ] ) dp [ i ] [ j - w[ i ] ] +c[ i ]= max( dp[ i - 1 ] [ j - w[ i ] ] + c[ i ], dp[ i - 1 ] [ j -2*w[ i ] ]+2*c[ i ] ,..., dp[ i -1 ] [ j - k*w[i] ] + k*c[ i ]),(0<k \le W/w[i]) dp[i][jw[i]]+c[i]=max(dp[i1][jw[i]]+c[i],dp[i1][j2w[i]]+2c[i],...,dp[i1][jkw[i]]+kc[i]),(0<kW/w[i])

由上述在选择的情况下可知:
必然从选择1次到选择W/w[i]次之间中产生一个最大值:
d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − w [ i ] ] + c [ i ] , d p [ i − 1 ] [ j − 2 ∗ w [ i ] ] + 2 ∗ c [ i ] , . . . , d p [ i − 1 ] [ j − k ∗ w [ i ] ] + k ∗ c [ i ] ) , ( 0 < k ≤ W / w [ i ] ) dp [ i ] [ j ] = max( dp[ i - 1 ] [ j ],dp[ i - 1 ] [ j - w[ i ] ] + c[ i ], dp[ i - 1 ] [ j -2*w[ i ] ]+2*c[ i ] ,..., dp[ i -1 ] [ j - k*w[i] ] +k*c[ i ]),(0< k \le W/w[i]) dp[i][j]=max(dp[i1][j],dp[i1][jw[i]]+c[i],dp[i1][j2w[i]]+2c[i],...,dp[i1][jkw[i]]+kc[i]),(0<kW/w[i])

显然①④右侧相等,所以左侧必然相等有:

dp[ i ][ j ] = dp[ i ][ j -w[ i ] ]+c[ i ]

在选择的情况下,这个式子实际上使用dp[ i ][ j -w[ i ]]代替了max(dp[i-1][ j - k * w[i])+k * c[ i ]) 这个需要循环找第i-1行最大值的式子,使用本行即第 i 行中已经计算出的式子来更新dp[ i ] [ j ] ,这个替换具有重要意义,直接去掉了一层循环。

#include <bits/stdc++.h>
#define maxlen 205

using namespace std;
int w[maxlen]; 
int c[maxlen]; 
int dp[35][205];
int main() {
	int m,n;
	cin>>m>>n;
	for(int i =1 ;i<=n; i++)
		cin>>w[i]>>c[i];
		
	//0.枚举每个物品 
	for(int i = 1 ;i<=n ;i++){
		for(int j = 1 ;j<=m ;j++){
			//1.先默认不选择的情况
			dp[i][j] = dp[i-1][j];
			if(j>=w[i])
				//2.使用本层的dp[i][j-w[i]]+c[i]替换了max(dp[i][j-k*w[i]]+k*c[i])
				dp[i][j] = max(dp[i][j],dp[i][j-w[i]]+c[i]);
		}
	} 
	cout<<dp[n][m]<<endl;
   return 0;
}
3.简化

和01背包类似,实际上多重背包dp[ i ][ j ]的计算只使用到了本行的内容,因此只用保存一行的内容即可。容易得到:

#include <bits/stdc++.h>
#define maxlen int(1e7+1)
long dp[maxlen];
long v[maxlen];
long w[maxlen];

using namespace std;

int main() {
	int n,W;
	cin>>W>>n;
	for(int i = 1 ;i<=n ;i++)
		cin>>w[i]>>v[i];
	
	for(int i = 1 ;i<=n ;i++){
		for(int j = 0 ;j<=W ;j++){
			if(j>=w[i]){
				dp[j] = max(dp[j],dp[j-w[i]]+v[i]);
			}
		}
	}
	cout<<dp[W]<<endl;
   return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值