题目描述:
- 有n种体积和价值分别为 w i , v i w_i,v_i wi,vi的物品。有一个容量为W(体积)的背包。求出背包能装下的最大价值,每种物品的数量是无限的。
解题思路:
- 动态规划法:
- dp数组含义: d p [ i ] [ j ] dp[i][j] dp[i][j]=从编号为 1 − i 1-i 1−i的物品中挑选物品放入容量为 j j j的背包中能得到的最大价值。注意:n种物品编号范围为1-n,0做作递推的起点。
- 初始条件: d p [ 0 ] [ 0 − W ] = 0 dp[0][0-W]=0 dp[0][0−W]=0
- 递推公式: d p [ i ] [ j ] = m a x { d p [ i − 1 ] [ j − k ∗ w [ i ] ] + k ∗ v [ i ] ∣ 0 ≤ k & k ∗ w [ i ] ≤ j } dp[i][j]=max\{dp[i-1][j-k*w[i]]+k*v[i]|0≤k\&k*w[i]≤j\} dp[i][j]=max{dp[i−1][j−k∗w[i]]+k∗v[i]∣0≤k&k∗w[i]≤j}
- 结果:结果在 d p [ n ] [ W ] dp[n][W] dp[n][W]中
- 代码:
#include
#include
using namespace std;
#define Max_n 105
#define Max_w 10005
int n,W;
int w[Max_n],v[Max_n];
int dp[Max_n][Max_w];
void initialize(int n,int m)
{
for(int i=0; i<=n; i++)
dp[i][0]=0;
for(int j=0; j<=W; j++)
dp[0][j]=0;
}
void solve_1()
{
for(int i=1; i<=n; i++)
for(int j=1; j<=W; j++)
{
dp[i][j]=-1;
for(int k=0; k*w[i]<=j; k++)
dp[i][j]=max(dp[i][j],dp[i-1][j-k*w[i]]+k*v[i]);
}
}
- 时间复杂度:代码的时间复杂度都是: O ( n W 2 ) O(nW^2) O(nW2)
- 动态规划法的一些总结:
- 递推动态规划一般情况的步骤:
- 第一步:确定dp数组的含义
- 第二步:确定递推的初始条件
- 第三步:确定递推公式
- 注意一:
- 第三步的递推的公式其实质是确定了递推的方向: d p [ i ] [ j ] = m a x { d p [ i − 1 ] [ j − k ∗ w [ i ] ] + k ∗ v [ i ] ∣ 0 ≤ k & k ∗ w [ i ] ≤ j } dp[i][j]=max\{dp[i-1][j-k*w[i]]+k*v[i]|0≤k\&k*w[i]≤j\} dp[i][j]=max{dp[i−1][j−k∗w[i]]+k∗v[i]∣0≤k&k∗w[i]≤j}确定的递推方向就是从上向下的一个递推方向,即只要知道矩阵的第i行就能推出矩阵的第i+1行:
- 可以使用不同的递推方向: d p [ i ] [ j ] = m a x { d p [ i ] [ j − 1 ] , m a x { d p [ i ] [ j − k ∗ w [ i ] ] + k ∗ v [ i ] ∣ 0 ≤ k & k ∗ w [ i ] ≤ j } } dp[i][j]=max \{dp[i][j-1],max\{dp[i][j-k*w[i]]+k*v[i]|0≤k\&k*w[i]≤j\}\} dp[i][j]=max{dp[i][j−1],max{dp[i][j−k∗w[i]]+k∗v[i]∣0≤k&k∗w[i]≤j}}它的递推方向就是从左下到右上方向,即要求 d p [ i ] [ j ] dp[i][j] dp[i][j]只要知道 d p [ i ] [ 0 − j ] ( 下 ) dp[i][0-j](下) dp[i][0−j](下)和 d p [ i − 1 ] [ j ] ( 左 ) dp[i-1][j](左) dp[i−1][j](左)
- 需要注意的是递推方向一定要以初始条件为基础。像公式1的起始条件是:dp[0][0-W]=0;而公式2的起始条件为:dp[0][0-W]=0和dp[0-n][0]=0
- 代码:
#include
#include
using namespace std;
#define Max_n 105
#define Max_w 10005
int n,W;
int w[Max_n],v[Max_n];
int dp[Max_n][Max_w];
void initialize(int n,int m)
{
for(int i=0; i<=n; i++)
dp[i][0]=0;
for(int j=0; j<=W; j++)
dp[0][j]=0;
}
void solve_2()
{
for(int i=1; i<=n; i++)
for(int j=1; j<=W; j++)
{
dp[i][j]=dp[i-1][j];
for(int k=1; k*w[i]<=j; k++)
dp[i][j]=max(dp[i][j],dp[i][j-k*w[i]]+k*v[i]);
}
}
- 时间复杂度:代码的时间复杂度都是: O ( n W 2 ) O(nW^2) O(nW2)
- 注意二:
- 还有一个地方需要注意:就是要保证递推的正确性,像下面公式就是错误的,它的递推反向是从左到右。 d p [ i ] [ j ] = m a x { d p [ i ] [ j − k ∗ w [ i ] ] + k ∗ v [ i ] ∣ 0 ≤ k & k ∗ w [ i ] ≤ j } dp[i][j]=max\{dp[i][j-k*w[i]]+k*v[i]|0≤k\&k*w[i]≤j\} dp[i][j]=max{dp[i][j−k∗w[i]]+k∗v[i]∣0≤k&k∗w[i]≤j}
时间复杂度的优化:
- 基本想法:
- 在 d p [ i ] [ j ] = m a x { d p [ i − 1 ] [ j − k ∗ w [ i ] ] + k ∗ v [ i ] ∣ 0 ≤ k & k ∗ w [ i ] ≤ j } dp[i][j]=max\{dp[i-1][j-k*w[i]]+k*v[i]|0≤k\&k*w[i]≤j\} dp[i][j]=max{dp[i−1][j−k∗w[i]]+k∗v[i]∣0≤k&k∗w[i]≤j}的递推下其实还是有重叠子问题:假设我们现在按照递推计算 d p [ i ] [ j ] dp[i][j] dp[i][j],并且假设 K ∗ w [ i ] < = j < ( K + 1 ) ∗ w [ i ] K*w[i]<=j<(K+1)*w[i] K∗w[i]<=j<(K+1)∗w[i],
- 于是我需要从这些选项: d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − w [ i ] ] + v [ i ] , d p [ i − 1 ] [ j − 2 ∗ w [ i ] ] + 2 ∗ v [ i ] , … , d p [ i − 1 ] [ j − K ∗ w [ i ] ] + K ∗ v [ i ] dp[i-1][j],dp[i-1][j-w[i]]+v[i],dp[i-1][j-2*w[i]]+2*v[i],…,dp[i-1][j-K*w[i]]+K*v[i] dp[i−1][j],dp[i−1][j−w[i]]+v[i],dp[i−1][j−2∗w[i]]+2∗v[i],…,dp[i−1][j−K∗w[i]]+K∗v[i]中选取最大值作为 d p [ i ] [ j ] dp[i][j] dp[i][j]
- 选项进行变形: d p [ i − 1 ] [ j ] , { d p [ i − 1 ] [ j − w [ i ] ] , d p [ i − 1 ] [ j − 2 ∗ w [ i ] ] + v [ i ] , … , d p [ i − 1 ] [ j − K ∗ w [ i ] ] + ( K − 1 ) ∗ v [ i ] } + v [ i ] dp[i-1][j],\{dp[i-1][j-w[i]],dp[i-1][j-2*w[i]]+v[i],…,dp[i-1][j-K*w[i]]+(K-1)*v[i]\}+v[i] dp[i−1][j],{dp[i−1][j−w[i]],dp[i−1][j−2∗w[i]]+v[i],…,dp[i−1][j−K∗w[i]]+(K−1)∗v[i]}+v[i],即将原来后面的K项提取一个公因式 v [ i ] v[i] v[i],现在就变成:先从 d p [ i − 1 ] [ j − w [ i ] ] , d p [ i − 1 ] [ j − 2 ∗ w [ i ] ] + v [ i ] , … , d p [ i − 1 ] [ j − K ∗ w [ i ] ] + ( K − 1 ) ∗ v [ i ] dp[i-1][j-w[i]],dp[i-1][j-2*w[i]]+v[i],…,dp[i-1][j-K*w[i]]+(K-1)*v[i] dp[i−1][j−w[i]],dp[i−1][j−2∗w[i]]+v[i],…,dp[i−1][j−K∗w[i]]+(K−1)∗v[i]选取一个最大值,令最大值为: Z Z Z,再从 d p [ i − 1 ] [ j ] , Z + v [ i ] dp[i-1][j],Z+v[i] dp[i−1][j],Z+v[i]中选取最大值作为 d p [ i ] [ j ] dp[i][j] dp[i][j]
- 现在我们考虑第一步:从 d p [ i − 1 ] [ j − w [ i ] ] , d p [ i − 1 ] [ j − 2 ∗ w [ i ] ] + v [ i ] , … , d p [ i − 1 ] [ j − K ∗ w [ i ] ] + ( K − 1 ) ∗ v [ i ] dp[i-1][j-w[i]],dp[i-1][j-2*w[i]]+v[i],…,dp[i-1][j-K*w[i]]+(K-1)*v[i] dp[i−1][j−w[i]],dp[i−1][j−2∗w[i]]+v[i],…,dp[i−1][j−K∗w[i]]+(K−1)∗v[i]选取一个最大值,我们将 j − w [ j ] j-w[j] j−w[j]看作一个整体得到: d p [ i − 1 ] [ ( j − w [ i ] ) ] , d p [ i − 1 ] [ ( j − w [ i ] ) − w [ i ] ] + v [ i ] , … , d p [ i − 1 ] [ j − w [ i ] − ( K − 1 ) ∗ w [ i ] ] + ( K − 1 ) ∗ v [ i ] dp[i-1][(j-w[i])],dp[i-1][(j-w[i])-w[i]]+v[i],…,dp[i-1][j-w[i]-(K-1)*w[i]]+(K-1)*v[i] dp[i−1][(j−w[i])],dp[i−1][(j−w[i])−w[i]]+v[i],…,dp[i−1][j−w[i]−(K−1)∗w[i]]+(K−1)∗v[i],这样我们可以看出其实它就是 d p [ i ] [ ( j − w [ j ] ) ] = m a x { d p [ i − 1 ] [ ( j − w [ i ] ) − k ∗ w [ i ] ] + k ∗ v [ i ] ∣ 0 ≤ k & k ∗ w [ i ] ≤ ( j − w [ i ] ) } dp[i][(j-w[j])]=max\{dp[i-1][(j-w[i])-k*w[i]]+k*v[i]|0≤k\&k*w[i]≤(j-w[i])\} dp[i][(j−w[j])]=max{dp[i−1][(j−w[i])−k∗w[i]]+k∗v[i]∣0≤k&k∗w[i]≤(j−w[i])},故这一步其实重复计算一次 d p [ i ] [ ( j − w [ j ] ) ] dp[i][(j-w[j])] dp[i][(j−w[j])]
- 数学推导:
d p [ i ] [ j ] = m a x { d p [ i − 1 ] [ j − k ∗ w [ i ] ] + k ∗ v [ i ] ∣ 0 ≤ k & k ∗ w [ i ] ≤ j } dp[i][j]=max\{dp[i-1][j-k*w[i]]+k*v[i]|0≤k\&k*w[i]≤j\} dp[i][j]=max{dp[i−1][j−k∗w[i]]+k∗v[i]∣0≤k&k∗w[i]≤j} = m a x { d p [ i − 1 ] [ j ] , m a x { d p [ i − 1 ] [ j − k ∗ w [ i ] ] + k ∗ v [ i ] ∣ 1 ≤ k & k ∗ w [ i ] ≤ j } } =ma x\{dp[i-1][j],max\{dp[i-1][j-k*w[i]]+k*v[i]|1≤k\&k*w[i]≤j\}\} =max{dp[i−1][j],max{dp[i−1][j−k∗w[i]]+k∗v[i]∣1≤k&k∗w[i]≤j}} = m a x { d p [ i − 1 ] [ j ] , m a x { d p [ i − 1 ] [ ( j − w [ i ] ) − ( k − 1 ) ∗ w [ i ] ] + ( k − 1 ) ∗ v [ i ] ∣ 0 ≤ ( k − 1 ) & k ∗ w [ i ] ≤ j } + v [ i ] } =ma x\{dp[i-1][j],max\{dp[i-1][(j-w[i])-(k-1)*w[i]]+(k-1)*v[i]|0≤(k-1)\&k*w[i]≤j\}+v[i]\} =max{dp[i−1][j],max{dp[i−1][(j−w[i])−(k−1)∗w[i]]+(k−1)∗v[i]∣0≤(k−1)&k∗w[i]≤j}+v[i]} = m a x { d p [ i − 1 ] [ j ] , m a x { d p [ i − 1 ] [ ( j − w [ i ] ) − k ∗ w [ i ] ] + k ∗ v [ i ] ∣ 0 ≤ k & ( k + 1 ) ∗ w [ i ] ≤ j } + v [ i ] } =max\{dp[i-1][j],max\{dp[i-1][(j-w[i])-k*w[i]]+k*v[i]|0≤k\&(k+1)*w[i]≤j\}+v[i]\} =max{dp[i−1][j],max{dp[i−1][(j−w[i])−k∗w[i]]+k∗v[i]∣0≤k&(k+1)∗w[i]≤j}+v[i]}并且 d p [ i ] [ j − w [ i ] ] = m a x { d p [ i − 1 ] [ ( j − w [ i ] ) − k ∗ w [ i ] ] + k ∗ v [ i ] ∣ 0 ≤ k & ( k + 1 ) ∗ v [ i ] ≤ j } dp[i][j-w[i]]=max\{dp[i-1][(j-w[i])-k*w[i]]+k*v[i]|0≤k\&(k+1)*v[i]≤j\} dp[i][j−w[i]]=max{dp[i−1][(j−w[i])−k∗w[i]]+k∗v[i]∣0≤k&(k+1)∗v[i]≤j}所以得到 p [ i ] [ j ] = m a x { d p [ i − 1 ] [ j ] , d p [ i ] [ j − w [ i ] ] + v [ i ] } p[i][j]=max\{dp[i-1][j],dp[i][j-w[i]]+v[i]\} p[i][j]=max{dp[i−1][j],dp[i][j−w[i]]+v[i]}
- 代码:
#include
#include
using namespace std;
#define Max_n 105
#define Max_w 10005
int n,W;
int w[Max_n],v[Max_n];
int dp[Max_n][Max_w];
void initialize(int n,int m)
{
for(int i=0; i<=n; i++)
dp[i][0]=0;
for(int j=0; j<=W; j++)
dp[0][j]=0;
}
\\p[i][j]=max{dp[i-1][j],dp[i][j-w[i]]+v[i]}
void solve_3()
{
for(int i=1; i<=n; i++)
for(int j=1; j<=W; j++)
{
dp[i][j]=dp[i-1][j];
if(w[i]<=j)
dp[i][j]=max(dp[i][j],dp[i][j-w[i]]+v[i]);
}
}
- 时间复杂度:代码的时间复杂度都是: O ( n W ) O(nW) O(nW)
空间复杂度的优化:
- 基本想法:
- 由上面的分析可知:我其实只需要最后保存 d p [ n ] [ W ] dp[n][W] dp[n][W]就可以了,并且由递推公式 p [ i ] [ j ] = m a x { d p [ i − 1 ] [ j ] , d p [ i ] [ j − w [ i ] ] + v [ i ] } p[i][j]=max\{dp[i-1][j],dp[i][j-w[i]]+v[i]\} p[i][j]=max{dp[i−1][j],dp[i][j−w[i]]+v[i]}可以知,我们在求dp[i][j]只依赖于dp[i][0-j]和dp[i-1][j]。即我们在求dp数组的第i行时我们只需要知道 d p [ i ] [ 0 ] dp[i][0] dp[i][0]和第 i − 1 i-1 i−1行就可以了。这样我们可以使用滚动数组来节省空间,即定义一个两行的数组滚动的循环使用这两行。
- 代码:
#include
#include
using namespace std;
#define Max_n 105
#define Max_w 10005
int n,W;
int w[Max_n],v[Max_n];
int dp_roll[2][Max_w];
void solve_4()
{
memset(dp_roll,0,sizeof(dp_roll));
for(int i=1; i<=n; i++)
for(int j=1; j<=W; j++)
{
dp_roll[i&1][j]=dp_roll[(i-1)&1][j];
if(j>=w[i])
dp_roll[i&1][j]=max(dp_roll[i&1][j],dp_roll[i&1][j-w[i]]+v[i]);
}
}
- 背包问题进一步优化:
- 在完全背包问题中我们的 d p [ i ] [ j ] dp[i][j] dp[i][j]只使用了上一行的一个元素 d p [ i − 1 ] [ j ] dp[i-1][j] dp[i−1][j]所以可以直接用一维数组实现。
- 代码:
#include
#include
using namespace std;
#define Max_n 105
#define Max_w 10005
int n,W;
int w[Max_n],v[Max_n];
int dp_reuse[Max_w];
void solve_5()
{
memset(dp_reuse,0,sizeof(dp_reuse));
for(int i=1; i<=n; i++)
for(int j=w[i]; j<=W; j++)
dp_reuse[j]=max(dp_reuse[j],dp_reuse[j-w[i]]+v[i]);
}