动态规划——简单背包问题

简单背包问题

一、01背包

回到目录
问题描述:
一个旅行者有一个最多能装 M 公斤的背包,现在有 n 件物品,它们的重量分别是W1,W2,…,Wn,它们的价值分别为C1,C2,…,Cn,求旅行者能获得最大总价值。

输入

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

输出

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

样例输入

10 4
2 1
3 3
4 5
7 9

样例输出

12

策略:

显然,对于每一个物品都只有两种状态,装和不装。对于一个子问题,我们可以用 d [ i ] [ j ] d[i][j] d[i][j]来表示在把前i个物品中装入容量为j的背包中所能够获得的最大的价值
那么可以很容易的得到状态转移方程为
d [ i ] [ j ] = { d [ i − 1 ] [ j ] ( j &lt; w [ i ] ) m a x ( d [ i − 1 ] [ j ] , d [ i − 1 ] [ j − w [ i ] ] + v [ i ] ) ( j ≥ w [ i ] ) d[i][j]= \left\{ \begin{aligned} d[i-1][j] &amp; &amp;({j &lt; w[i]})\\ max(d[i-1][j],d[i-1][j-w[i]]+v[i]) &amp; &amp;({j \geq w[i]}) \\ \end{aligned} \right. d[i][j]={d[i1][j]max(d[i1][j],d[i1][jw[i]]+v[i])(j<w[i])(jw[i])

这样就通过双重循环去从前往后的填充这个 d d d这个二维数组,这样每一次填充的时候,都能够满足最优子结构,且无后效性,都是最优的这样最终的答案就是 d [ n ] [ m ] d[n][m] d[n][m]
根据样例填充的表格如下

纵轴:物品编号
横轴:当前的最大重量
012345678910
100111111111
200133444444
300135568899
40013456991012

代码

#include <cstdio>
#include <algorithm>
#define MAXN 205
#define MAXX 35
int d[MAXX][MAXN], w[MAXX], v[MAXX];
int main() {
	int n, m;
	scanf("%d %d", &m, &n); 
	for(int i = 1; i <= n; i++) 
		scanf("%d %d", &w[i], &v[i]);
	for(int i = 1; i <= n; i++) {
		for(int j = 1; j <= m; j++) {
			if(j < w[i])
				d[i][j] = d[i-1][j];
			else
				d[i][j] = std::max(d[i-1][j], d[i-1][j-w[i]]+v[i]);
		}
	}
	printf("%d\n", d[n][m]);
	return 0;
}

空间优化:

可以发现,对于二维的数组来讲,每更新当前一层的时候,只与上一层有关,可以从这里进行优化。
使用一维数组去表示,考虑d[i]表示背包中重量不超过i的最大价值,同样的状态转移方程如下
d [ j ] = m a x ( d [ j ] , d [ j − w [ i ] ] + v [ i ] ) &ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace; ( j ≥ w [ i ] ) d[j] = max(d[j], d[j-w[i]]+v[i]) \,\,\,\,\,(j\geq w[i]) d[j]=max(d[j],d[jw[i]]+v[i])(jw[i])
对于一维数组我们的填充就要考虑从后往前填充( j − &gt; w [ i ] j-&gt;w[i] j>w[i]),因为我们要保证随着每考虑一个物品的时候,我们都要尽可能的装入当前的能装的背包,同时要基于上一层的情况进行下去,才能找出最优情况.
至于从前往后填充为什么不行,可以这样理解,因为在每一次更新 d [ j ] d[j] d[j]的值的时候,我们需要保证, d [ j ] d[j] d[j]前面的值是选择上一个物品时的情况,如果从前往后填充,随着j的增大, d [ j − w [ i ] ] + v [ i ] d[j-w[i]]+v[i] d[jw[i]]+v[i]一定是大于 d [ j ] d[j] d[j]本身的,而且会越来越大,这样d[j]也没有记录上一层的情况(因为此时每种物品只能使用一次),那么d[j-w[i]]+v[i]会不断的更新,d[j]的值会越来越大,因为v[i]和之前的d[j]会被反复叠加。
可以总结出,逆推的关键就是因为每种物品只能选择一次

如果要求恰好装满背包,需要将 d d d数组其他初始化无穷小, d [ 0 ] = 0 d[0]=0 d[0]=0
代码

#include <cstdio>
#include <algorithm>
#define MAXN 205
#define MAXX 35
int d[MAXN], w[MAXX], v[MAXX];
int main() {
	int n, m;
	scanf("%d %d", &m, &n); 
	for(int i = 1; i <= n; i++) 
		scanf("%d %d", &w[i], &v[i]);
	for(int i = 1; i <= n; i++) 
		for(int j = m; j >= w[i]; j--) //逆推
			d[j] = std::max(d[j], d[j-w[i]]+v[i]);
	printf("%d\n", d[m]);
	return 0;
}

回到目录

二、完全背包

问题描述:

设有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

策略分析:

现在的情况是每个物品可以选择多个,那么久不再是选择或者不选的情况了,情况变成了每个物品可以选择 0 、 1 、 2 、 3... K 0、1、2、3...K 0123...K件,只要装的下,就可以放进去,那么基于之前的 01 01 01背包,如果还是用 d p [ i ] [ j ] dp[i][j] dp[i][j]来表示从前i件物品中选择重量不超过j的物品的最大价值那么很容易得到状态转移方程为
d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j − k ∗ w [ i ] ] + k ∗ v [ i ] &ThinSpace;&ThinSpace; ( 0 ≤ k ∗ w [ i ] ≤ j ) ) dp[i][j] = max( dp[i-1][j-k*w[i]]+k*v[i]\,\, (0\leq k*w[i] \leq j)) dp[i][j]=max(dp[i1][jkw[i]]+kv[i](0kw[i]j))

代码

#include <iostream>
#include <algorithm>
using namespace std;
#define N 35
#define M 205

int dp[N][M];
int w[N], v[N];

int main () {
	int n, m;
	cin >> m >> 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 <= m; j++) {
			if(j < w[i])
				dp[i][j] = dp[i-1][j];
			else {
				int t = j / w[i];
				for(int k = 0; k <= t; k++)
					dp[i][j] = max(dp[i][j], dp[i-1][j-k*w[i]]+v[i]*k);
			}
		}
	}
	cout << "max=" << dp[n][m];
	return 0;
}

同样进行优化

由于完全背包的每一个物品可以多次使用个。考虑 01 01 01背包中的一维数组实现方法,只需要将其中的逆推改为顺推即可,因为每件物品可以无限次的使用。那么在考虑是否装同一种物品的时候,肯定是能装下的范围内,越多越好。状态转移方程如下,和 01 01 01背包相同,不过实现的时候使用顺推
d [ j ] = m a x ( d [ j ] , d [ j − w [ i ] ] + v [ i ] ) &ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace; ( j ≥ w [ i ] ) d[j] = max(d[j], d[j-w[i]]+v[i]) \,\,\,\,\,(j\geq w[i]) d[j]=max(d[j],d[jw[i]]+v[i])(jw[i])

代码

#include <iostream>
#include <algorithm>
using namespace std;
#define N 35
#define M 205

int dp[M];
int w[N], v[N];

int main () {
	int n, m;
	cin >> m >> n;
	for(int i = 1; i <= n; i++) 
		cin >> w[i] >> v[i];
	for(int i = 1; i <= n; i++) {
		for(int j = w[i]; j <= m; j++) //顺推
			dp[j] = max(dp[j], dp[j-w[i]]+v[i]);
	}
	cout << "max=" <<dp[m];
	return 0;
}

回到目录

三、多重背包

问题描述:

为了庆贺班级在校运动会上取得全校第一名成绩,班主任决定开一场庆功会,为此拨款购买奖品犒劳运动员。期望拨款金额能购买最大价值的奖品,可以补充他们的精力和体力

输入

第一行二个数n(n ≤ 500),m(m ≤ 6000),其中n代表希望购买的奖品的种数,m表示拨款金额。

接下来n行,每行3个数,v、w、s,分别表示第I种奖品的价格、价值(价格与价值是不同的概念)和能购买的最大数量(买0件到s件均可),其中v ≤ 100,w ≤ 1000,s ≤ 10。

输出

一行:一个数,表示此次购买能获得的最大的价值(注意!不是价格)。
样例输入

5 1000
80 20 4
40 50 9
30 50 7
40 30 6
20 20 1

样例输出

1040

策略分析:

多重背包,类似于完全背包,不过有限制,每种物品规定的使用的数量不超过 s s s,只需改一下状态转移方程即可
d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j − k ∗ w [ i ] ] + k ∗ v [ i ] &ThinSpace;&ThinSpace; ( 0 ≤ k ≤ n u m [ i ] ) ) dp[i][j] = max( dp[i-1][j-k*w[i]]+k*v[i]\,\, (0\leq k \leq num[i])) dp[i][j]=max(dp[i1][jkw[i]]+kv[i](0knum[i]))

代码

#include <iostream>
#include <algorithm>
#define N 505
#define M 6005
using namespace std;

int dp[N][M];
int w[N], v[N], num[N];

int main () {
	int n, m;
	cin >> n >> m;
	for(int i = 1; i <= n; i++)	
		cin >> w[i] >> v[i] >> num[i];
		
	for(int i = 1; i <= n; i++) {
		for(int j = 0; j <= m; j++) {
			if(j < w[i])
				dp[i][j] = dp[i-1][j];
			else {
				int t = j / w[i];
				for(int k = 0; k <= t && k <= num[i]; k++)
					dp[i][j] = max(dp[i][j], dp[i-1][j-k*w[i]] + k * v[i]);
			}
		}
	}
	cout << dp[n][m];
	return 0;
}

回到目录

四、混合背包

问题描述:

一个旅行者有一个最多能装V公斤的背包,现在有n件物品,W1,W2,…,Wn,它们的价值分别为C1,C2,…,Cn。有的物品只可以取一次(01背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个上限(多重背包)。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

输入

第一行:二个整数,M(背包容量,M ≤ 200),N(物品数量,N ≤ 30);

第2…N+1行:每行三个整数Wi,Ci,Pi,前两个整数分别表示每个物品的重量,价值,第三个整数若为0,则说明此物品可以购买无数件,若为其他数字,则为此物品可购买的最多件数(Pi)。。

输出

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

样例输入

10  3
2  1  0
3  3  1
4  5  4

样例输出

11

提示
选第一件物品1件和第三件物品2件。

策略分析:

混合背包就是将前三种背包结合起来使用,那么显然对于不同的背包使用不同的状态转移方程即可。

代码

#include <iostream>
#include <algorithm>
#define N 35
#define M 205
using namespace std;

int dp[M];
int w[N], v[N], num[N];

int main () {
	int n, m;
	cin >> m >> n;
	for(int i = 1; i <= n; i++)	
		cin >> w[i] >> v[i] >> num[i];
	for(int i = 1; i <= n; i++) {
		if(num[i] == 1) { //01背包
			for(int j = m; j >= w[i]; j--)
				dp[j] = max(dp[j], dp[j-w[i]] + v[i]);
		}else if(num[i] == 0) { //完全背包
			for(int j = w[i]; j <= m; j++) 
				dp[j] = max(dp[j], dp[j-w[i]] + v[i]);
		}else { //多重背包
			for(int j = m; j >= 0; j--) {
				for(int s = 0; s <= num[i]; s++)
					if(j >= s*w[i])
						dp[j] = max(dp[j], dp[j-s*w[i]] + s * v[i]);
			}
		}
	}
	cout << dp[m];
	return 0;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值