完全背包问题

题目描述:

  • 有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 1i的物品中挑选物品放入容量为 j j j的背包中能得到的最大价值。注意:n种物品编号范围为1-n,0做作递推的起点。
  • 初始条件: d p [ 0 ] [ 0 − W ] = 0 dp[0][0-W]=0 dp[0][0W]=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[i1][jkw[i]]+kv[i]0k&kw[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;
}

//dp[i][j]=max⁡{dp[i-1][j-k*w[i]]+k*v[i]|0≤k&k*w[i]≤j}
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[i1][jkw[i]]+kv[i]0k&kw[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][j1],max{dp[i][jkw[i]]+kv[i]0k&kw[i]j}}它的递推方向就是从左下到右上方向,即要求 d p [ i ] [ j ] dp[i][j] dp[i][j]只要知道 d p [ i ] [ 0 − j ] ( 下 ) dp[i][0-j](下) dp[i][0j]() d p [ i − 1 ] [ j ] ( 左 ) dp[i-1][j](左) dp[i1][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;
}

//dp[i][j]=max⁡{(dp[i][j-k*w[i]]+k*v[i]|0≤k&k*w[i]≤j),dp[i][j-1]}
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][jkw[i]]+kv[i]0k&kw[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[i1][jkw[i]]+kv[i]0k&kw[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] Kw[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[i1][j],dp[i1][jw[i]]+v[i],dp[i1][j2w[i]]+2v[i],,dp[i1][jKw[i]]+Kv[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[i1][j],{dp[i1][jw[i]],dp[i1][j2w[i]]+v[i],,dp[i1][jKw[i]]+(K1)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[i1][jw[i]],dp[i1][j2w[i]]+v[i],,dp[i1][jKw[i]]+(K1)v[i]选取一个最大值,令最大值为: Z Z Z,再从 d p [ i − 1 ] [ j ] , Z + v [ i ] dp[i-1][j],Z+v[i] dp[i1][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[i1][jw[i]],dp[i1][j2w[i]]+v[i],,dp[i1][jKw[i]]+(K1)v[i]选取一个最大值,我们将 j − w [ j ] j-w[j] jw[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[i1][(jw[i])],dp[i1][(jw[i])w[i]]+v[i],,dp[i1][jw[i](K1)w[i]]+(K1)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][(jw[j])]=max{dp[i1][(jw[i])kw[i]]+kv[i]0k&kw[i](jw[i])},故这一步其实重复计算一次 d p [ i ] [ ( j − w [ j ] ) ] dp[i][(j-w[j])] dp[i][(jw[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[i1][jkw[i]]+kv[i]0k&kw[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[i1][j],max{dp[i1][jkw[i]]+kv[i]1k&kw[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[i1][j],max{dp[i1][(jw[i])(k1)w[i]]+(k1)v[i]0(k1)&kw[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[i1][j],max{dp[i1][(jw[i])kw[i]]+kv[i]0k&(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][jw[i]]=max{dp[i1][(jw[i])kw[i]]+kv[i]0k&(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[i1][j],dp[i][jw[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[i1][j],dp[i][jw[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 i1行就可以了。这样我们可以使用滚动数组来节省空间,即定义一个两行的数组滚动的循环使用这两行。

- 代码:

#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];//&1相当于%2
            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[i1][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]);
}

你可能感兴趣的:(ACM)