背包问题

Posted wangguodong

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了背包问题相关的知识,希望对你有一定的参考价值。

01背包(ZeroOnePack): 有N件物品和一个容量为V的背包,每种物品均只有一件,第i件物品的重量是w[i],价值是v[i]。求解将哪些物品装入背包可使价值总和最大。

完全背包(CompletePack): 有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的重量是w[i],价值是v[i]。求解将哪些物品装入背包可使这些物品的重量总和不超过背包容量,且价值总和最大。

01背包

对于01背包,每一个物体都有两种选择,放或者不放。我们可以使用一个二维数组dp[i][j]来模拟这个过程。dp[i][j]表示在背包容量为j的限制下,在前面i个(包括第i个)的物品中选择所能获取到的最大价值。于是,可以得到如下的状态转移方程:

dp[i][j]=0 i=0或j=0

dp[i][j]=max(dp[i-1][j-w[i]]+v[i], dp[i-1][j]) i≠0且j≠0

上面的第二个等式基于如下事实:若dp[i][j]表示在给定容量为j的背包中,选择前面i个物品所获取的最大价值,那么如果决定选择第i+1个物品,则dp[i+1][j+w[i+1]]=dp[i][j]+v[i+1]。这两个等式其实是等价的。当然,如果不选择第i+1个物品,dp[i+1][j]=dp[i][j]。

事实上,我们只需要一个一维数组即可完成上述迭代,使用dp[j]表示在当前状态下容量为j的背包所可以获取到的最大价值,这里说的“当前状态”指的是下面代码的一次外循环。

for(int i = 1; i <= n; i++){

  for(int j = V; j >= w[i]; j--){

    dp[j] = max(dp[j - w[i]] + v[i], dp[j]);

  }  

}

值得注意的是,上面的内循环是逆循环。因为只有这样才能保证该状态和前一状态的递推关系。

完全背包

这里我们仍然使用一维数组dp来表示迭代过程,同理,dp[j]表示在当前状态下容量为j的背包所可以获取到的最大价值。

for(int i = 1; i <= n; i++){

  for(int j = w[i]; j <= V; j++){

    dp[j] = max(dp[j - w[i]] + v[j], dp[j]);

  }  

}

内循环是正循环二者的根本差异体现在物体i是否可以重复拿取。对于完全背包来说,当内循环的j=n*w[i](n为非负整数)时,等价于将n个物品i放入背包中。

 

例题

题目:一个正整数n分解成x(1≤x≤$10^{6}$)个正整数的立方和,求x最小值是多少?

可能初看该题目属于贪心问题,类似于“找零钱”的题目,但是该题并不具备数字上的倍数关系,如96=64+8+8+8+8=64+27+1+1+1+1+1,答案明显是5,而贪心会算到7[1]其实,该题应该属于完全背包问题,同一个数可以被选择多次。我们可以将每一个正整数的立方看成是一个物品,目标是求将容量为n的背包填满,并且使得物品的个数最少。我们使用dp[j]表示填满容量为j的背包所需的最小物品个数。因此可以很容易写出状态转移方程:

初始化:   dp[0] = 0

迭代过程: dp[j] = min(dp[j - i * i * i] + 1, dp[j])

迭代过程的含义:j-i*i*i可以通过加上i*i*i转移到j,一共需要(dp[j-i*i*i]+1)个正整数,将其与上一个状态的dp[j]比较,选择最小值即可。

#include<math.h>
#include<iostream>
#define INF 0x3fffffff;
using namespace std;
int dp[1000010];
int main()
{int n;
    while(cin >> n){
        for(int i = 0; i <= n; i++) dp[i] = INF;
        int x = pow(n, 1. / 3) + 1;
        for(int i = 1; i <= x; i++){
            int low_bound = i * i * i;
            dp[0] = 0;
            for(int j = low_bound; j <= n; j++){
                dp[j] = min(dp[j], dp[j - low_bound] + 1);
            }
        }
        cout << dp[n] << endl;
    }
    return 0;
}

 

参考:

 

以上是关于背包问题的主要内容,如果未能解决你的问题,请参考以下文章

使用喷气背包导航将自定义过渡动画添加到底部导航设置

在片段的后按防止使用导航图调用前一个片段的 onViewCreated

Android Jetpack 导航禁用滚动位置

0-1背包问题的回溯法代码

c语言背包问题,求高手解答

动态规划_01背包_完全背包_多重背包_分组背包