DP学习笔记(8):完全背包求方案数,01背包求具体方案

完全背包求方案数

常规分析

在上一篇我们学习了01背包求方案数,今天我们学习完全背包求方案数。

首先我们要区分一下01背包和完全背包的区别,01背包中的物品只有一个只有选或不选完全背包中的物品有无限件实际有m/w[i]件,可以多选

我们在学习01背包求方案数时,要将 j 倒序来避免多选问题,在完全背包上我们需要多选,所以将 j 改为正序循环就可以满足我们的需求

核心的状态和状态转移方程都是一样的

状态:dp[j]前i件物品在背包容量不超过j的情况下的最多方案数

状态转移方程:if(j>=w[i]) dp[j]+=dp[j-w[i]]

下面我们使用一道例题来验证我们的想法是否正确

例题:奥赛一本通——1293:买书

【题目描述】

小明手里有n元钱全部用来买书,书的价格为10元,20元,50元,100元。

问小明有多少种买书方案?(每种书可购买多本)

【输入】

一个整数 n,代表总共钱数。(0≤n≤1000)

【输出】

一个整数,代表选择方案种数。

【输入样例】

20

【输出样例】

2

【提示】

样例输入

样例输入2:

15

样例输入3:

0

样例输出

样例输出2:

0

样例输出3:

0

题目分析: 

钱全花完,可以看作要求可以凑出n的方法数,并且n=0时 答案为0,但是我们需要dp[0]=1来进行后面的计算,所以n=0要单独拿出来讨论,下面给代码,然后推理一下dp表

#include
using namespace std;
const int N = 1e3 + 10, M = 1e3 + 10;
int w[N];//w-容量,价格
int dp[M];
/*
状态:dp[j]前i件物品在背包容量不超过j的情况下的最多方案数
*/
int main()
{
	int n;
	cin >> n;
	if (n == 0)
	{
		cout << 0;
		return 0;
	}
	w[1] = 10, w[2] = 20, w[3] = 50, w[4] = 100;
	dp[0] = 1;
	for (int i = 1; i <= 4; i++) {
		for (int j = w[i]; j <= n ; j++) {
			dp[j] += dp[j - w[i]];
		}
	}
	cout << dp[n];
}

就拿n=20为例

i=1时 w[1]=10;

dp[10] =dp[10]+dp[10-10]=0+1=1;表示用 10 凑出 10 的方案数,下面都是一样的就不一 一阐述

dp[11] =dp[11]+dp[11-10]=0+0=0;表示用 10 凑出 11 的方案数,很明显是 0

dp[20]=dp[20]+dp[20-10]=0+dp[10]=1;表示用 10 凑出 20 的方案数

i=2时 w[i]=20

dp[20]=dp[20]+dp[20-20]=1+1=2;表示 用10,20 凑出20的方案数

i=3,i=4都是一样的

下面给出dp表

dp[1][10]=1

dp[1][11]=0

dp[1][12]=0

dp[1][13]=0

dp[1][14]=0

dp[1][15]=0

dp[1][16]=0

dp[1][17]=0

dp[1][18]=0

dp[1][19]=0

dp[1][20]=1
dp[2][20]=2

01背包求具体方案 

常规分析

求具体方案问题,我们在序列dp中也接触过,使用的时pre数组记录前驱节点的方法,这个方法在我们01背包求具体方案中也是可以使用的,具体的不多说,我们来说说另一个方法,在最终的dp[n][m]被求出之后,我们逆着回找那件物品选了没有,01背包的状态和状态转移方程这里不多说,放在下面的代码注释部分,不了解的可以再看一下。

这里主要给大家说一下我们在判断物品被选与否时的判断语句

 if(w[i]<=j&&dp[i][j] == dp[i - 1][j - w[i]] + c[i])

首先 j>=w[i] 就不多说了 用来保证物品可以放下,同时处理越界问题,再进行一部分剪枝,优化代码

我们来说一下 dp[i][j] == dp[i - 1][j - w[i]] + c[i] 首先我们发现这个dp数组是二维数组,因为我们要先求出整个dp表,再利用dp表来找具体方案,所以这个方法不可以使用滚动数组优化因为滚动数组会覆盖先前的dp导致数据丢失

再说原理,根据01背包的状态转移方程dp[i][j]满足dp[i][j] == dp[i - 1][j - w[i]] + c[i],是不是就说明 dp[i][j] 是由 dp[i - 1][j - w[i]] 转移来的,因为我们遍历 i ,所以我们更新 j 就可以令 dp[i][j]=dp[i - 1][j - w[i]],就是 j-=w[i],再进行下次的寻找,是不是有点递归的味道了,我们在这个 “递归” 的过程中记录走过的路径就找到了我们要的方案

由于我没有找到相应的例题,就给大家一个代码供大家参考一下

#include
#include
using namespace std;
const int N = 1e3 + 10, M = 1e3 + 10;
int w[N],c[N];//w-容量,价格,c-价值
int dp[N][M];
/*
状态:dp[i][j]前i件物品在背包容量不超过j的情况下的最多方案数
状态转移方程:
if (w[i] > j) dp[i][j] = dp[i - 1][j];
else dp[i][j] = max(dp[i][j], dp[i-1][j - w[i]] + c[i]);
*/
int main()
{
	int m, n;
	cin >> m >> n;
	for (int i = 1; i <= n; i++) {
		cin >> w[i] >> c[i];
	}
	//边界dp[0]=0
	for (int i = 1; i <= n; i++) {
		for (int j = m; j >= 1; j--) {
			if (w[i] > j) dp[i][j] = dp[i - 1][j];
			else dp[i][j] = max(dp[i][j], dp[i-1][j - w[i]] + c[i]);
		}
	}
	//求最大价值
	//cout << dp[n][m];
	//求具体方案
	vector res;
	for (int i = n, j = m; i >= 1; i--) {
		//证明第i件物品选了
		if (j >= w[i] && dp[i][j] == dp[i - 1][j - w[i]] + c[i]) {
			res.push_back(i);
			j -= w[i];
		}
	 }
	for (auto it : res)  cout<< it << " ";
}

你可能感兴趣的:(DP学习笔记(8):完全背包求方案数,01背包求具体方案)