最详细的背包问题!!!

Posted on 2022-04-23 15:58  ZheyuHarry  阅读(222)  评论(1编辑  收藏  举报

我们知道,背包问题是一种非常经典的动态规划问题,本文总结了几种类型的背包问题并进行了分析:

 

根据百度百科,我们可以看到背包问题(Knapsack problem)是一种组合优化的NP完全问题。问题可以描述为:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。问题的名称来源于如何选择最合适的物品放置于给定背包中。相似问题经常出现在商业、组合数学,计算复杂性理论、密码学和应用数学等领域中。也可以将背包问题描述为决定性问题,即在总重量不超过W的前提下,总价值是否能达到V?它是在1978年由Merkle和Hellman提出的。

 

一般来讲,我们可以将其分为一下几种类型:

·01背包问题

·完全背包问题

·多重背包问题

·组合背包问题

 

我们就一个一个的来解决,第一个我们先看到最基础的01背包问题:

01背包就是告诉我们有n件物品,每件物品你要么选要么不选,最多选一次;

 

我们先考虑一下朴素算法,也就是暴力穷举的方法,但是发现很明显这样有2^n种可能性,这是不可接受的  。  而我们这里如果是用动态规划的话可以有效的将时间复杂度降低到O(NV),这里我们就要把问题空间中的目标和状态给提取出来,所以我们定义状态为:

dp[i][j]表示只有前i件物品考虑放进去或者不放进去,总体积不超过j时,所能达到的最大价值。

我们先把所有的dp初始化为0,然后我们考虑要不要装入第i件物品,有以下两种可能:

1.不装入第i件物品,则dp[i][j] = dp[i-1][j];

2.装入第i件物品的情况下,dp[i][j] = dp[i-1][j - v[i] ] + w[i];

这是dp[i][j]的在考察到第i件物品时的两种 可能,所以我们可以得出状态转移方程如下:

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

 

而且,我们可以发现,dp[i][j]至于dp[i-1][0~j-1]有关,所以我们可以使用动态规划十分常用的空间优化(滚动数组,即去掉dp的第一维),然后在第i层下,如果dp[j]未被更新过,就是dp[i-1][j],如果被更新过了就是dp[i][j]了,所以我们可以发现进行空间优化之后,我们不能覆盖上一层的循环,所以枚举体积j的时候只能逆向枚举,这是为了避免拿更新过的数值继续更新同一维的数值;

 

这里体现了动态规划的核心思想就是避免重复计算,第i件物品装入与不装入而获得的最大价值完全可以由前i-1件物品的最大价值决定,而暴力枚举忽略了这个事实!

 

代码:

#include<bits/stdc++.h>
#define maxn 1010

using namespace std;
int v[maxn],w[maxn];
int f[maxn];
int n,m;

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 = m;j>=v[i];j--)
f[j] = max(f[j - v[i]] + w[i], f[j]);
cout << f[m] << '\n';
return 0;
}

 

接下来,我们接着考虑第二种背包问题也就是完全背包问题

完全背包问题和上面的01背包问题不一样的是,完全背包问题中第i件物品有无穷的后备,可以想拿多少拿多少;

我们有常见的两种分析思路,首先我们可以和01背包问题一样先定义一下状态:

dp[i][j]表示只有前i件物品考虑放进去或者不放进去,总体积不超过j时,所能达到的最大价值。

 

但是我们在这里考虑第i件物品要不要加一个进去 的时候,我们不用考虑dp[i-1][j - v[i] ] , 而是转移到dp[i][j - v[i] ]中,也就是说在加入了第i件商品之后是否还可以加入第i件商品;

 

我们也可以写成:

dp[i][j] = max(dp[i-1][j] , dp[i-1][j - v[i] ] + w[i] , dp[i-1][j - 2*v[i] ] + 2*w[i] ……,dp[i-1][j - k*v[i] ] + k*w[i] )

dp[i][j - v[i] ] = max(dp[i-1][j - v[i] ]  , dp[i-1][j - 2*v[i] ] + w[i] ……,dp[i-1][j - k*v[i] ] + (k-1)*w[i] )

我们可以发现上面红色的部分只差了w[i],所以我们可以将状态转移方程写为:

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

 

这是第一种分析方式,接下来是第二种分析方式,就是直接把上面的k一个个列出来取最值,比较麻烦,所以这里不赘述了!

 

代码:

#include<bits/stdc++.h>
#define maxn 1010

using namespace std;
int v[maxn],w[maxn];
int f[maxn];

int main()
{
int n,m;
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++)
f[j] = max(f[j],f[j - v[i]] + w[i]);
cout << f[m] << '\n';

return 0;
}

 

这里同样可以进行空间优化,是同样的方法,但是我们发现因为我们状态转移方程的时候,用到了dp[i][j - v[i] ],所以说我们可以用更新后的数值去更新同一维的数值,所以这里我们不需要进行逆向穷举可以直接进行正向循环枚举;

 

接下来的而这个问题称为多重背包问题,是因为这里的第i件物品既不像01背包物品中那样限定最多选一件,也没有像完全背包问题中那样可以选无限多件,也是可以根据数据的复杂程度,我们分为两种分析方式:

·················未完待续···············