01 背包

题目链接: 01 背包

题意: 有 n 种物品要放到一个袋子里,袋子的总容量为 m,第 i 种物品的体积为 vi ,把它放进袋子里会获得 wi 的收益,每种物品至多能用一次,问如何选择物品,使得在物品的总体积不超过 m 的情况下,获得最大的收益?请求出最大收益。

输入格式:
 第一行两个整数 n, m。
 接下来 n 行,每行两个整数vi, wi

输出格式:
 一个整数,表示答案。

样例输入:

5 10
5 3
3 6
7 8
5 9
2 4

样例输出

19

Step1 :

思路分析:
  01 背包问题作为经典的动态规划问题。我们拿到题目该如何考虑呢?首先我们要考虑该如何表示各种状态,通过对题目分析可以看出我们需要记录的信息有取到第 i i i 个物品,取到第 i i i 个物品时的总体积,以及此时的最大收益。因此我们可以使用 f [ i , j ] f [i, j] f[i,j] 来表示取到第 i i i 个物品时(取到而不是取了),总体积为 j j j。同时 f [ i ] [ j ] f[i][j] f[i][j] 的值表示此时能获得的最大收益。
  接下来我们考虑状态之间是如何转移的。显然对于第 i i i 件物品我们有取和不取两种选择,如果不取第 i i i 件物品,那么此时的最大收益 f [ i ] [ j ] f[i][j] f[i][j] 就是取到前 i − 1 i - 1 i1 物品体积为 j j j 的最大收益,即 f [ i − 1 ] [ j ] f[i-1][j] f[i1][j]。如果我们取第 i i i 件物品,此时的最大收益就是取到前 i − 1 i - 1 i1 个物品体积为 j − v [ i ] j - v[i] jv[i] 的最大收益加上第 i i i 件物品的收益,即 f [ i − 1 ] [ j − v [ i ] ] + w [ i ] f[i-1][j - v[i]] + w [i] f[i1][jv[i]]+w[i]。综上,取到第 i i i 个物品,总体积为 j j j 时的最大收益 f [ i ] [ j ] f[i][j] f[i][j] 即为上面两个情况收益的较大者,所以我们得到的状态转移方程如下:
f [ i ] [ j ] = m a x { f [ i − 1 ] [ j ] 不取第 i 个物品 f [ i − 1 ] [ j − v [ i ] ] + w [ i ] 取第 i 个物品 f [i][j]=max\begin{cases} f [i-1][j] & 不取第 i 个物品 \\ f[i - 1][j - v[i]] + w[i] & 取第 i 个物品 \end{cases} f[i][j]=max{f[i1][j]f[i1][jv[i]]+w[i]不取第i个物品取第i个物品

参考代码:

#include <bits/stdc++.h>

using namespace std;

#define IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)

const int N = 1005;

int n, m;
int v[N], w[N], f[N][N];

int main()
{
    IO;
    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 = 0; j <= m; ++ j)
            if(j < v[i]) // 空间不够只能不取
                f[i][j] = f[i - 1][j];
            else
                f[i][j] = max(f[i - 1][j], f[i - 1][j - v[i]] + w[i]);
    cout << f[n][m] ;
    return 0;
}

Step2 :

优化思路 :
  通过Step 1我们实现了对01 背包问题的求解,但我们发现记录状态(即 f [ N ] [ N ] f[N][N] f[N][N])所用的空间较大,那么我们在解决问题的过程中真的要使用这么大的空间吗?我们通过分析状态转移方程可知:第 i i i 个的状态只和第 i − 1 i - 1 i1 个状态有关,如下图所示:
Step2
  所以我们便得到了优化思路:使用 g g g 数组记录本轮的状态,用 f f f 数组记录上一轮状态,本轮更新完成后再将 f f f 数组更新为 g g g 数组,如此往复便可以求解出最终答案。

参考代码:

#include <bits/stdc++.h>

using namespace std;

#define IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)

const int N = 1005;

int n, m;
int v[N], w[N], f[N], g[N];

int main()
{
    IO;
    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 = 0; j <= m; ++ j)
        {
            if(j < v[i])// 空间不够只能不取
                g[j] = f[j];
            else
                g[j] = max(f[j], f[j - v[i]] + w[i]);

        }
        memcpy(f, g, sizeof(g)); // 用 g 更新 f
    }     
    cout << f[m] ;
    return 0;
}

Step3 :

优化思路:
  通过Step2我们已经将空间优化到只需要用到 f f f g g g 两个数组,那么我们能不能再优化一步,只用一个 f f f 数组记录就可以呢?在这里我们再画个图看一看:
Step3
  如果我们只使用数组 f f f,我们在更新第 i i i 轮时, f f f 数组中存储的是 i − 1 i - 1 i1的信息,而我们通过图可以看出,在更新第 i i i 轮的第 j j j 个位置时,使用的是第 i − 1 i-1 i1 轮的第 j j j 个位置或者第 j − v [ i ] j-v[i] jv[i] 个位置,显然就是此时的 f [ j ] f[j] f[j] f [ j − v [ i ] ] f[j-v[i]] f[jv[i]],显然这种优化的思路是可行的。但需要注意一点,在更新第 i i i 轮时, j j j 需要从大到小去枚举体积。为什么呢?如果我们从小到大去进行枚举,假如我们已经更新了第 j − v [ i ] j-v[i] jv[i] 这个位置,那么在更新 j j j 这个位置时,我们需要使用 j − v [ i ] j-v[i] jv[i] 这个位置第 i − 1 i-1 i1 轮的信息,但此时 j − v [ i ] j-v[i] jv[i] 这个位置上的信息已经是第 i i i 轮的信息,显然这样操作我们得到的是错误的答案。通过上面的分析,我们便完成了更近一步的优化。

参考代码:

#include <bits/stdc++.h>

using namespace std;

#define IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)

const int N = 1005;

int n, m;
int v[N], w[N], f[N];

int main()
{
    IO;
    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)
        	// 那么为什么枚举到v[i]就可以了???
            f[j] = max(f[j], f[j - v[i]] + w[i]);
    cout << f[m] ;
    return 0;
}

End:
  01 背包问题的求解分享就到这里结束啦,如果对大家有帮助的话,大家一定要点赞 + 收藏哦!!!

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值