完全背包问题

题目描述:

  • 有n种体积和价值分别为wi,viw_i,v_iwi,vi的物品。有一个容量为W(体积)的背包。求出背包能装下的最大价值,每种物品的数量是无限的。

解题思路:

- 动态规划法:

  • dp数组含义:dp[i][j]dp[i][j]dp[i][j]=从编号为1−i1-i1i的物品中挑选物品放入容量为jjj的背包中能得到的最大价值。注意:n种物品编号范围为1-n,0做作递推的起点。
  • 初始条件:dp[0][0−W]=0dp[0][0-W]=0dp[0][0W]=0
  • 递推公式: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\}dp[i][j]=max{dp[i1][jkw[i]]+kv[i]0k&kw[i]j}
  • 结果:结果在dp[n][W]dp[n][W]dp[n][W]

- 代码:

#include <iostream>
#include <string.h>
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(nW2)O(nW^2)O(nW2)

- 动态规划法的一些总结:

  • 递推动态规划一般情况的步骤:
    • 第一步:确定dp数组的含义
    • 第二步:确定递推的初始条件
    • 第三步:确定递推公式
  • 注意一
    • 第三步的递推的公式其实质是确定了递推的方向dp[i][j]=max⁡{dp[i−1][j−k∗w[i]]+k∗v[i]∣0≤k&amp;k∗w[i]≤j}dp[i][j]=max⁡\{dp[i-1][j-k*w[i]]+k*v[i]|0≤k\&amp;k*w[i]≤j\}dp[i][j]=max{dp[i1][jkw[i]]+kv[i]0k&kw[i]j}确定的递推方向就是从上向下的一个递推方向,即只要知道矩阵的第i行就能推出矩阵的第i+1行:
    • 可以使用不同的递推方向:dp[i][j]=max{dp[i][j−1],max{dp[i][j−k∗w[i]]+k∗v[i]∣0≤k&amp;k∗w[i]≤j}}dp[i][j]=max \{dp[i][j-1],max\{dp[i][j-k*w[i]]+k*v[i]|0≤k\&amp;k*w[i]≤j\}\}dp[i][j]=max{dp[i][j1],max{dp[i][jkw[i]]+kv[i]0k&kw[i]j}}它的递推方向就是从左下到右上方向,即要求dp[i][j]dp[i][j]dp[i][j]只要知道dp[i][0−j](下)dp[i][0-j](下)dp[i][0j]()dp[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 <iostream>
#include <string.h>
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(nW2)O(nW^2)O(nW2)
  • 注意二
    • 还有一个地方需要注意:就是要保证递推的正确性,像下面公式就是错误的,它的递推反向是从左到右。dp[i][j]=max{dp[i][j−k∗w[i]]+k∗v[i]∣0≤k&amp;k∗w[i]≤j}dp[i][j]=max\{dp[i][j-k*w[i]]+k*v[i]|0≤k\&amp;k*w[i]≤j\}dp[i][j]=max{dp[i][jkw[i]]+kv[i]0k&kw[i]j}

时间复杂度的优化:

- 基本想法:

  • dp[i][j]=max⁡{dp[i−1][j−k∗w[i]]+k∗v[i]∣0≤k&amp;k∗w[i]≤j}dp[i][j]=max⁡\{dp[i-1][j-k*w[i]]+k*v[i]|0≤k\&amp;k*w[i]≤j\}dp[i][j]=max{dp[i1][jkw[i]]+kv[i]0k&kw[i]j}的递推下其实还是有重叠子问题:假设我们现在按照递推计算dp[i][j]dp[i][j]dp[i][j],并且假设K∗w[i]&lt;=j&lt;(K+1)∗w[i]K*w[i]&lt;=j&lt;(K+1)*w[i]Kw[i]<=j<(K+1)w[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]dp[i1][j],dp[i1][jw[i]]+v[i],dp[i1][j2w[i]]+2v[i],,dp[i1][jKw[i]]+Kv[i]中选取最大值作为dp[i][j]dp[i][j]dp[i][j]
    • 选项进行变形: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]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],现在就变成:先从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]dp[i1][jw[i]],dp[i1][j2w[i]]+v[i],,dp[i1][jKw[i]]+(K1)v[i]选取一个最大值,令最大值为:ZZZ,再从dp[i−1][j],Z+v[i]dp[i-1][j],Z+v[i]dp[i1][j],Z+v[i]中选取最大值作为dp[i][j]dp[i][j]dp[i][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]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]看作一个整体得到: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]dp[i1][(jw[i])],dp[i1][(jw[i])w[i]]+v[i],,dp[i1][jw[i](K1)w[i]]+(K1)v[i],这样我们可以看出其实它就是dp[i][(j−w[j])]=max⁡{dp[i−1][(j−w[i])−k∗w[i]]+k∗v[i]∣0≤k&amp;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\&amp;k*w[i]≤(j-w[i])\}dp[i][(jw[j])]=max{dp[i1][(jw[i])kw[i]]+kv[i]0k&kw[i](jw[i])},故这一步其实重复计算一次dp[i][(j−w[j])]dp[i][(j-w[j])]dp[i][(jw[j])]

- 数学推导:

dp[i][j]=max⁡{dp[i−1][j−k∗w[i]]+k∗v[i]∣0≤k&amp;k∗w[i]≤j}dp[i][j]=max⁡\{dp[i-1][j-k*w[i]]+k*v[i]|0≤k\&amp;k*w[i]≤j\}dp[i][j]=max{dp[i1][jkw[i]]+kv[i]0k&kw[i]j}=max⁡{dp[i−1][j],max{dp[i−1][j−k∗w[i]]+k∗v[i]∣1≤k&amp;k∗w[i]≤j}}=ma x⁡\{dp[i-1][j],max\{dp[i-1][j-k*w[i]]+k*v[i]|1≤k\&amp;k*w[i]≤j\}\}=max{dp[i1][j],max{dp[i1][jkw[i]]+kv[i]1k&kw[i]j}}=max⁡{dp[i−1][j],max{dp[i−1][(j−w[i])−(k−1)∗w[i]]+(k−1)∗v[i]∣0≤(k−1)&amp;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)\&amp;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]}=max{dp[i−1][j],max{dp[i−1][(j−w[i])−k∗w[i]]+k∗v[i]∣0≤k&amp;(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\&amp;(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]}并且dp[i][j−w[i]]=max⁡{dp[i−1][(j−w[i])−k∗w[i]]+k∗v[i]∣0≤k&amp;(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\&amp;(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]=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]\}p[i][j]=max{dp[i1][j],dp[i][jw[i]]+v[i]}

- 代码:

#include <iostream>
#include <string.h>
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(nW)O(nW)O(nW)

空间复杂度的优化:

- 基本想法:

  • 由上面的分析可知:我其实只需要最后保存dp[n][W]dp[n][W]dp[n][W]就可以了,并且由递推公式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]\}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行时我们只需要知道dp[i][0]dp[i][0]dp[i][0]和第i−1i-1i1行就可以了。这样我们可以使用滚动数组来节省空间,即定义一个两行的数组滚动的循环使用这两行。

- 代码:

#include <iostream>
#include <string.h>
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]);
        }
}

- 背包问题进一步优化:

  • 在完全背包问题中我们的dp[i][j]dp[i][j]dp[i][j]只使用了上一行的一个元素dp[i−1][j]dp[i-1][j]dp[i1][j]所以可以直接用一维数组实现。

- 代码:

#include <iostream>
#include <string.h>
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]);
}
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值