动态规划小结——背包问题

背包问题是动态规划的经典问题,所有初次学习动态规划都会讲述这个经典问题。因此,有必要弄清跟背包问题的所有分析过程并熟练掌握各种类型的代码。

一,完全背包问题

1.问题描述:有n种物品,每种物品有无限多个,第i个物品重量是wi,价值是vi,从这些物品中挑选总重量不超过W的物品,求出挑选物品价值的最大值。

限制条件:1≤n≤100,1≤wi,vi≤100,1≤W≤10000

2.解题思路:本题类似于“硬币问题”,硬币问题只要求凑够相应的面值,这里只不过多了一个新的属性——价值,即不仅要凑够重量,还要使得价值尽可能大。按照动态规划的分析思路,先定义状态d(j):剩余重量为j时的最大价值。那么如果当前使用了第i个物品(如果可以用的话),状态变转移到j-w[i],而价值变为d[j-w[i]]+v[i],因此状态转移方程如下:

d[j]=max(d[j],d[j-w[i]]+v[i]);(0≤i<n)

这个过程中随着物品的增多,剩余的重量在逐渐减小。

3.代码:

(1)不包含打印路径

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<algorithm>
#include<string>
#include<sstream>
#include<set>
#include<vector>
#include<stack>
#include<map>
#include<queue>
#include<deque>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<ctime>
#include<functional>
using namespace std;

#define maxn 1000
#define rep(i,n) for(int i=0;i<(n);i++)
int d[maxn];
int w[maxn],v[maxn];
int n, W;
int dp(int j)
{
	int&ans = d[j];
	if (ans>0)return ans;
	for (int i = 0; i < n;i++)
	if (j >= w[i])
		ans = max(ans, dp(j-w[i]) + v[i]);
	return ans;
}
int main()
{
	//freopen("test.txt", "r", stdin);
	scanf("%d%d", &n, &W);
	rep(i, n)
		scanf("%d", w + i);
	rep(i, n)
		scanf("%d", v + i);
	dp(W);//从状态W开始,走到状态0
	cout << d[W] << endl;
	return 0;
}

(2)包含打印路径

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<algorithm>
#include<string>
#include<sstream>
#include<set>
#include<vector>
#include<stack>
#include<map>
#include<queue>
#include<deque>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<ctime>
#include<functional>
using namespace std;

#define maxn 1000
#define rep(i,n) for(int i=0;i<(n);i++)
int d[maxn];
int w[maxn],v[maxn];
int n, W;
int dp(int j)
{
	int&ans = d[j];
	if (ans>0)return ans;
	for (int i = 0; i < n;i++)
	if (j >= w[i])
		ans = max(ans, dp(j-w[i]) + v[i]);
	return ans;
}
void print_ans(int j)
{
	for (int i = 0; i < n;i++)
	if (j >= w[i] && d[j] == d[j - w[i]] + v[i])
	{
		printf("%d ", i);
		print_ans(j - w[i]);
		break;
	}
}
int main()
{
	//freopen("test.txt", "r", stdin);
	scanf("%d%d", &n, &W);
	rep(i, n)
		scanf("%d", w + i);
	rep(i, n)
		scanf("%d", v + i);
	dp(W);//从状态W开始,走到状态0
	cout << d[W] << endl;
	print_ans(W);//打印所用物品的序号
	puts("");
	return 0;
}

二,0-1背包问题

1.问题描述:有n种物品,每种只有一个,,第i个物品重量是wi,价值是vi,从这些物品中挑选总重量不超过W的物品,求出挑选物品价值的最大值。

限制条件:1≤n≤100,1≤wi,vi≤100,1≤W≤10000

2.解题思路:现在的问题又增加了一个限制条件——每种物品的个数,可以发现,原来的状态转移方程已经不适用了,因为只凭借“剩余重量的大小”无法得知每个物品是否用过,因此需要修改原来定义的状态,使得以后的决策可以有序化。

想象所有的物品按照序号摆成一排,现在,定义d(i,j)表示当前正在第i个物品处,背包的剩余容量为j时的最大价值。显然,这时的决策有两个:(1)不使用物品i;(2)使用物品i;因此,状态转移方程如下:

d(i,j)=max(d(i+1,j),d(i+1,j-w[i])+v[i]);(j≥w[i])

边界:d(n-1,j)=0;

上式中,i要逆序枚举,因为公式中i时候的状态是由i+1时候的得到的。上式中的j随着i的减小而增大,这也不难由公司看出。最后的答案是d[0][W],这样代码便不难写出。

3.代码:

(1)不包含打印解

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<algorithm>
#include<string>
#include<sstream>
#include<set>
#include<vector>
#include<stack>
#include<map>
#include<queue>
#include<deque>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<ctime>
#include<functional>
using namespace std;

#define maxn 1000
#define rep(i,n) for(int i=0;i<(n);i++)
int d[maxn][maxn];
int w[maxn],v[maxn];
int n, W;

int main()
{
	//freopen("test.txt", "r", stdin);
	memset(d, -1, sizeof(d));
	scanf("%d%d", &n, &W);
	rep(i, n)
		scanf("%d", w + i);
	rep(i, n)
		scanf("%d", v + i);
	for (int i = n - 1; i >= 0;i--)//i逆序枚举,由递推公式决定
	for (int j = 0; j <= W; j++)//j的顺序无所谓
	{
		d[i][j] = (i == n - 1 ? 0 : d[i + 1][j]);
		if (j >= w[i])
			d[i][j] = max(d[i][j], d[i + 1][j - w[i]] + v[i]);
	}
	printf("%d\n", d[0][W]);
	return 0;
}

(2)包含打印解

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<algorithm>
#include<string>
#include<sstream>
#include<set>
#include<vector>
#include<stack>
#include<map>
#include<queue>
#include<deque>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<ctime>
#include<functional>
using namespace std;

#define maxn 1000
#define rep(i,n) for(int i=0;i<(n);i++)
int d[maxn][maxn];
int w[maxn],v[maxn];
int vis[maxn];
int n, W;

int main()
{
	//freopen("test.txt", "r", stdin);
	memset(d, -1, sizeof(d));
	scanf("%d%d", &n, &W);
	rep(i, n)
		scanf("%d", w + i);
	rep(i, n)
		scanf("%d", v + i);
	for (int i = n - 1; i >= 0; i--)//i逆序枚举,由递推公式决定
	for (int j = 0; j <= W; j++)//j的顺序无所谓
	{
		d[i][j] = (i == n - 1 ? 0 : d[i + 1][j]);
		if (j >= w[i])
		d[i][j] = max(d[i][j], d[i + 1][j - w[i]] + v[i]);
	}
	printf("%d\n", d[0][W]);
	printf("Solutions :");//打印已经选择的物品
	int R = W;
	for (int i = 0; i < n;i++)
	for (int j = R; j >= 0;j--)
	if (d[i][j] == d[i + 1][j])break;
	else if (j >= w[i] && d[i][j] == d[i + 1][j - w[i]] + v[i])
	{
		printf(" %d", i);
		R -= w[i];
		break;
	}
	printf("\n");
	return 0;
}

三,0-1背包问题的规划方向

刚刚的状态d(i,j)的定义是: 当前正在第i个物品处,背包的剩余容量为j时的最大价值。这样的定义导致了枚举i时必须逆序。其实还有一种“对称”的状态定义:当前正在第i个物品处,背包的总容量为j时的最大价值。按照这样的定义,不难得到如下的状态转移方程:

d(i,j)=max(d(i-1,j),d(i-1,j-w[i])+v[i])(j≥w[i]);

边界:d(0,j)=0;

按照上式,如果选择了物品i,那么在i-1处背包的容量必须是j-w[i],这样才能装下i物品。由上式可知,i必须顺序枚举,因为i处的状态是由i-1处的状态得到的。而且j随着i的增大而增大,这和之前说的剩余容量在逐渐减小是一致的。最终的答案是d(n-1,W)代码与上面的类似。

3.代码:

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<algorithm>
#include<string>
#include<sstream>
#include<set>
#include<vector>
#include<stack>
#include<map>
#include<queue>
#include<deque>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<ctime>
#include<functional>
using namespace std;

#define maxn 1000
#define rep(i,n) for(int i=0;i<(n);i++)
int d[maxn][maxn];
int w[maxn],v[maxn];
int vis[maxn];
int n, W;

int main()
{
	//freopen("test.txt", "r", stdin);
	memset(d, -1, sizeof(d));
	scanf("%d%d", &n, &W);
	rep(i, n)
		scanf("%d", w + i);
	rep(i, n)
		scanf("%d", v + i);
	for (int i = 0; i < n; i++)//i顺序枚举,由递推公式决定
	for (int j = 0; j <= W; j++)//j的顺序无所谓
	{
		d[i][j] = (i == 0 ? 0 : d[i - 1][j]);
		if (j >= w[i])
		d[i][j] = max(d[i][j], d[i - 1][j - w[i]] + v[i]);
	}
	printf("%d\n", d[n - 1][W]);
	return 0;
}

其实,这种对称的定义方法还有一个额外的好处,就是允许边读入边计算,而不必把w,v保存下来。

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<algorithm>
#include<string>
#include<sstream>
#include<set>
#include<vector>
#include<stack>
#include<map>
#include<queue>
#include<deque>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<ctime>
#include<functional>
using namespace std;

#define maxn 1000
#define rep(i,n) for(int i=0;i<(n);i++)
int d[maxn][maxn];
int w, v;
int vis[maxn];
int n, W;

int main()
{
	//freopen("test.txt", "r", stdin);
	memset(d, -1, sizeof(d));
	scanf("%d%d", &n, &W);
	for (int i = 0; i < n; i++)//i顺序枚举,由递推公式决定
	{
		scanf("%d%d", &w, &v);//读入第i个物品的重量,价值
		for (int j = 0; j <= W; j++)
		{
			d[i][j] = (i == 0 ? 0 : d[i - 1][j]);
			if (j >= w)d[i][j] = max(d[i][j], d[i - 1][j - w] + v);
		}
	}
	printf("%d\n", d[n - 1][W]);
	return 0;
}

不仅如此,甚至可以把d数组降维,将其变成一维数组。利用原来的结果产生新的结果,节约内存空间。这是因为在更新d(i,j)之前,它保存的就是上一次刚刚算过的d(i-1,j)的值。同理,如果j要逆序枚举,d(i,j-w[i])保存的也是上次刚刚算过的d(i-1,j-w[i])的值。因此,忽略i,即可实现降维。

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<algorithm>
#include<string>
#include<sstream>
#include<set>
#include<vector>
#include<stack>
#include<map>
#include<queue>
#include<deque>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<ctime>
#include<functional>
using namespace std;

#define maxn 1000
#define rep(i,n) for(int i=0;i<(n);i++)
int d[maxn];
int w, v;
int vis[maxn];
int n, W;

int main()
{
	freopen("test.txt", "r", stdin);
	memset(d, 0, sizeof(d));//将d数组初始化为0
	scanf("%d%d", &n, &W);
	for (int i = 0; i < n; i++)//i顺序枚举,由递推公式决定
	{
		scanf("%d%d", &w, &v);//读入第i个物品的重量,价值
		for (int j = W; j >= 0;j--)
		if (j >= w)
			d[j] = max(d[j], d[j - w] + v);
	}
	printf("%d\n", d[W]);
	return 0;
}

上述代码也被称为“滚动数组”,正是因为它相当于在不断地刷新之前的结果。但这种方法容易产生bug,而且很难打印解。当动态规划结束后,只有最后一个阶段的值,而没有前面的值。因此这种方法应该谨慎使用。



时间: 2024-10-27 05:46:28

动态规划小结——背包问题的相关文章

动态规划:背包问题

例题:装箱问题 ( http://www.wikioi.com/problem/1014/  ) 题目描述 有一个箱子容量为V(正整数,0<=V<=20000),同时有n个物品(0<n<=30),每个物品有一个体积(正整数). 要求n个物品中,任取若干个装入箱内,使箱子的剩余空间为最小. 输入描述 一个整数v,表示箱子容量,一个整数n,表示有n个物品 接下来n个整数,分别表示这n 个物品的各自体积 输出描述 一个整数,表示箱子剩余空间. 样例输入 24 6 8 3 12 7 9 7

算法导论三剑客之 动态规划 0-1背包问题

1 #include "iostream" 2 using namespace std; 3 4 float MAX(float m1,float m2){ 5 if(m1>=m2) 6 return m1; 7 else 8 return m2; 9 } 10 11 float bag_Zero_One(int n,float v,float p[],float w[]){ 12 if(n==0||v==0) 13 return 0; 14 else{ 15 float m2;

动态规划——0-1背包问题

 0-1背包问题: 描述:给定n中物品和一背包.物品i的重量是wi,其价值为vi,背包的容量为c,问应如何选择装入背包中的物品,使得装入背包中的物品总价值最大? 0-1背包问题是一个特殊的整数规划问题. 设所给0-1背包问题的子问题; 其最优值为m(i,j),即m(i,j)是背包容量为j,可选择物品为i,i+1,-,n时0-1背包问题的最优值.由0-1背包问题的最优子结构性质,可以建立计算m(i,j)的递归式如下: NO1:递归实现 1 /* 2 *Description 递归实现 3 *设有一

动态规划01背包问题

动态规划0-1背包问题 ? 问题描写叙述: 给定n种物品和一背包.物品i的重量是wi,其价值为vi,背包的容量为C.问应怎样选择装入背包的物品,使得装 入背包中物品的总价值最大? ? 对于一种物品,要么装入背包,要么不装.所以对于一种物品的装入状态能够取0和1.我们设物品i的装入状态为xi,xi∈ (0,1),此问题称为0-11背包问题. 过程分析 数据:物品个数n=5,物品重量w[n]={0,2,2,6,5,4},物品价值V[n]={0,6,3,5,4,6}, (第0位,置为0,不參与计算,仅

动态规划之背包问题

背包问题是一个经典的算法问题,可以用动态规划,贪心法,分支界限法等方法解决.问题描述:有n个物品,编号1,2,3,..n,其中第 i 个物品重量为Wi 价值 Vi ,有一个容量为W的背包.在容量允许范围内,如何选择物品,可以得到最大的价值.(为了简单起见,假设物品的重量 Wi 和价值Vi 都是正数) 今天主要说的是0.1背包问题,解法是动态规划.当然,对于另外两种问题也会有所介绍. 问题分析: 用动态规划解问题首先要有效的找出子问题,可以通过这个子问题得推得原问题的解,通常子问题的实质是和原问题

动态规划初探 -- 背包问题

在为期一个星期的ACM集训之后,我就这样做了一个逃兵hhhh 在这一个星期里面,学长讲了快速排序,二分三分搜索,矩阵快速幂,线段树,BFS(广度优先搜索)和DFS(深度优先搜索),邻接表和哈希表,结构体和优先队列,背包问题和动态规划. 其中讲快速排序那天我还在考试,就没有去听,第二天找学长的时候也听得似懂非懂. 学长讲矩阵快速幂的时候爸妈来找我,也没有听.其中基本都学的不扎实. 所以都要后期重新再学一遍,巩固扎实.估计学长还会讲并查集和最小生成树,我就自己看看书吧~ ==============

【动态规划/多重背包问题】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

【算法数据结构Java实现】Java实现动态规划(背包问题)

1.背景 追随着buptwusuopu大神的脚步,最近在研习动态规划.动态规划应该叫一种解决问题的思想,记得又一次去某公司面试就被问到了这个. 多于动态规划的理解,大致是这样的,从空集合开始,每增加一个元素就求它的最优解,直到所有元素加进来,就得到了总的最优解. 比较典型的应用就是背包问题,有一个重量一定的包,有若干件物品,他们各自有不同的重量和价值,怎样背才能取得最大价值. 错误的理解:去价值/重量比最大的物品多装(之前我也是这么想的,但是在背包重量一定的情况下这么做并不合理,范例很容易想到)

动态规划0-1背包问题

最近看了一些简单的动态规划方面的例题 在学习的过程中发现 有的问题虽然不难 但是第一次看还是会有些问题 所以把自己弄0-1背包的问题拿出来给大家分享 不喜勿喷 网上资源特别多 讲解什么的就算了 其他人画的图都不错 递推关系: 设所给0-1背包问题的最优值为m(i,j),即m(i,j)是背包容量为j,可选择物品为i,i+1,-,n时0-1背包问题的最优值.由0-1背包问题的最优子结构性质,可以建立计算m(i,j)的递归式: <!--[endif]--> 上式此时背包容量为j,可选择物品为i.此时