背包问题九讲-阅读与思考

动态规划则具有记忆性,通过填写表把所有已经解决的子问题答案纪录下来,在新问题里需要用到的子问题可以直接提取,避免了重复计算,从而节约了时间。

问题满足最优性原理之后,用动态规划解决问题的核心就在于从边界条件开始填一个dp[i][j]的表。表填写完毕,最优解也就找到。

最优性原理是动态规划的基础,最优性原理是指“多阶段决策过程的最优决策序列具有这样的性质:不论初始状态和初始决策如何,对于前面决策所造成的某一状态而言,其后各阶段的决策序列必须构成最优策略”。

01背包问题

题目:
将N件商品放入容量为V的书包,每件商品占据的容量空间为c[i],且其价值用w[i]表示。问将哪些物品放到背包可以使背包里面商品的价值最大。
填表:

  • 初始化边界条件:
    f(0,j)=f(i,0)=0;
  • 状态转移方程:
    f[i][v]用来表示将前i件物品放入当前容量v的书包得到的最大价值量,则状态转移方程表示为:
    f[i][v] = f[i-1][v]  (v<c[i])
    f[i][v] = max(f[i-1][v],f[i-1][v-c[i]]+w[i]])  (v>=c[i])

公式里的前一种情况为不放入第i件物品的情况,后一种放入第i件物品的情况。
表格填完,即可得到最优解。
时空复杂度均为O(N*V)。

  • 优化空间复杂度为O(V):
for i=1..N:
   for v=V..0:
         dp[v] = max(dp[v],dp[v-c[i]]+w[i])

注意:
此时空间大小的循环从V减至0而不是从0增至V的原因是,我们只使用了一个一维数组来保存背包能装载的最大价值,这个一维数组在内层循环处于不同背包容量时,对外层循环不同的物品数量来不断更新背包此刻的最大价值。
我们需要明白一点:dp[v] = max(dp[v],dp[v-c[i]]+w[i])式子里,max函数内的dp[v]与等号左边的dp[v]并不是相同的,max函数内dp数组的值应该都由上一层循环时物品数量决定(比如i此刻为2,代表在容量空间为v的书包选择前两个物品时能获得的最大价值,而i=2时的dp[v]的值需要通过i=1时的dp[v]或者i=1时的dp[v-c[i]]计算得到)。
如果此时v的循环从0到V,那么我们先计算得到了固定i个数的情况下,较小容量时背包的最大价值,那么我们在使用dp[v-c[i]]时,dp[v-c[i]]已经被当前i=2的循环更新,我们使用的dp[v-c[i]]不再是i=1时的情况,导致计算i错误。
按照二维数组的状态转移方程类比一维数组的动态转移方程:i值的状态应该由i-1值的状态决定。

  • 初始化的细节问题
    “恰好装满书包”则初始化时dp[0]为0,dp[1…V]为-∞,因为初始化时背包容量为0,只有0状态的背包满足要求,其他都不合法;
    “价格尽量大”则dp[0…V]都设为0,即任何容量的书包都可以“什么都不装”。

完全背包问题

题目:
将N件商品放入容量为V的书包,每件商品都可以用无限次,第i件商品的体积为c[i],其价值为w[i]。问将哪些物品放到背包可以使不超过背包的容量,且使背包里面商品的价值最大。
状态转移方程:

    f[i][v] = max(f[i-1][v-k*c[i]]+k*w[i]])  (0<=k*c[i]<=v)

复杂度:
O(VNk)

如果背包中只放一种物品则可以放V/c[i]件物品,因此可以把第i种物品转换为最多为V/c[i]件的体积和价值均不变的物品。

伪代码如下:

for(int i=1;i<N;i++)
{
    for(int v=V;v>0;v--)
    {
        for(int k=0;k<=(V/c[i]);k++)
        {
            if(k*c[i]<=v)
                f[i][v]=max{f[i-1][v],f[i-1][v-k*c[i]]+k*w[i]};
            else
                f[i][v]=f[i-1][v];
        }
    }
}

优化:
几乎与01背包的一维解答一模一样,唯一的区别是v的遍历次序是递增的。为什么呢?想一想,01背包问题中使v从V到0倒序进行计算的原因是保证f[i][v]的状态由f[i-1][v-c[i]]递推而来,而当每个物品可以选择无限次的时候,是否拿第i个物品所依赖的状态可能是已经取过若干个第i个物品的情况,所以需要顺序循环。

复杂度:
O(V*N)

代码:

#include <iostream>
#include <algorithm>

using namespace std;

const int N= 1010;

int n,m;

int arr[N];
int v[N];
int w[N];

int main()
{
    cin >> n >> m;

    for(int i = 1;i <= n;i++){
        cin >> v[i] >> w[i];
    }

    for(int i = 1;i<=n;i++){
        for(int j = v[i];j <= m ;j++){
            arr[j] = max(arr[j] , arr[j-v[i]]+w[i]);
        }
    }

    cout << arr[m];

    return 0;   
}

多重背包问题

题目:
将N件商品放入容量为V的书包,第i件商品最多可以用n[i]次,第i件商品的体积为c[i],其价值为w[i]。问将哪些物品放到背包可以使不超过背包的容量,且使背包里面商品的价值最大。
状态转移方程:

    f[i][v] = max(f[i-1][v-k*c[i]]+k*w[i]])  (0<=k<=n[i], 0<=k*c[i]<=v)

复杂度:
O(V*∑n[i])

代码:

#include<iostream>

using namespace std;
int main()
{
    int m,n;
    cin>>m>>n;
    int a[10001],b[10001],c[10001];
    for(int i=1;i<=n;i++){
        cin>>a[i]>>b[i]>>c[i];
    }
    int f[10001];
    for(int i=1;i<=n;i++)
    for(int j=m;j>=0;j--)
    for(int k=0;k<=c[i];k++){
        if(j-k*a[i]<0)break;
        f[j]=max(f[j],f[j-k*a[i]]+k*b[i]);
    }
    cout<<f[m]<<endl;//最优解
}

二进制优化:

如果一个物品在你的背包最大承重范围内的个数太多,这就要装好几次01背包,所以用二进制优化可以缩短同一物品使用01背包的次数。

优化过程:

一个数a,我们可以按照二进制来分解为1 + 2 + 4 + 8 …… +2^n + 剩下的数 = a;
我们把a拆成这么多项,可以证明,这么多项可以组合出1~a的每一个数。那么相比于循环a次,优化之后我们只需要循环log(a)次,分别确定1、2、4、8… …等取或者不取。

伪代码:

procedure MultiplePack(cost,weight,amount):
	if(cost*amount>=V)
		CompletePack(cost,weight) //因为当cost*weight大于背包的容量时,背包容量对该物品个数取值已经做了限制,问题简化为完全背包问题
		return;
    int k=1;
    while(k<amount):
    	ZeroOnePack(k*cost,k*weight);
    	amount = amount-k;
    	k=k*2;
   ZeroOnePack(amount*cost,amount*weight);

混合背包问题

01背包混合完全背包:在这里插入图片描述
再加上多重背包:
在这里插入图片描述

二维费用的背包问题

对于每件物品,都具有两种不同的代价,选择时需要同时考虑这两种代价,且每个代价都对应一个背包容量最大值上限(即背包容量也为二维)。在这里插入图片描述

分组的背包问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值