背包一讲

视频链接:背包九讲专题_哔哩哔哩_bilibili

 

 

一,01 背包问题  

1,二维代码

  f [ i ][ j ] 表示只考虑到前 i 个物品,且总体积恰好等于 j 的情况下,背包的最大价值。

  递推式:

    如果不选第 i 个物品,则 ①:f [ i ][ j ] = f [ i - 1 ][ j ] 

    如果选择第 i 个物品,则 ②:f [ i ][ j ] = f [ i - 1 ][ j - v[i] ] + w[ i ]

    f [ i ][ j ] = max(①, ②)

在写代码时,可以写成下面这种比较简洁。

1                 f[i][j] = f[i - 1][j]; // 默认不选
2                 if (v[i] <= j)         // 如果可以选,即物品体积 <= 背包体积
3                     f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);

 

2,一维代码:01 背包的空间优化

  因为在二维的 f 中,f[ i ][ ] 只与 f[ i-1 ][ ] 有关,所以可以将 f 数组压缩成一维。

  设置滚动数组 f [ j ]  表示:

    如果当前考虑的是第 i 个物品,那么在总体积小于等于 j 的情况下,只考虑到前 i 个物品的背包的最大价值是 f[ j ]。

  注意点:为什么内层循环要从后往前递推呢?

      因为 f[ j ] 是滚动数组,在实际代码运行的时候,针对每个 i,f[ ] 的值都会被刷新。

      而需要注意的是,当 i++ 后,进入一轮新的循环的时候,此时 f[ j ] 针对的是前 i-1 个物品。因为此时的 f[ j ] 还没有刷新,变成针对前 i 个物品。

      所以有:

        Ⅰ如果是从前往后遍历的话,那么 f[ j ] 肯定没经过刷新,代表的是针对前 i-1 个物品,而 ① 要求的是针对前 i 个物品,所以不行。

          且有 f[ j - v[ i ] ] 肯定经过刷新,代表的是针对前 i 个物品,而 ② 要求的是针对前 i-1 个物品,所以不行。

        Ⅱ 如果是从后往前遍历的话,那么 f[ j ] 肯定经过刷新,代表的是针对前 i 个物品,与 ① 的要求是相同的,所以可以。

          且有 f[ j - v[ i ] ] 肯定没经过刷新,代表的是针对前 i-1 个物品,与 ② 的要求是相同的,所以可以。

  递推式:

    从针对 i-1 个物品的状态,推导针对前 i 个物品的状态。

      原先,如果不选第 i 个物品,则 f [ i ][ j ] = f [ i - 1 ][ j ] ,

        此时,压缩成一维后,未刷新的 f[ j ] 等同于 f [ i - 1 ][ j ],

        所以有,

          ①:f [ j ] = f [ j ] 

      原先,如果选择第 i 个物品,则 f [ i ][ j ] = f [ i - 1 ][ j - v[i] ] + w[ i ]

        此时,压缩成一维后,未刷新的 f[ j - v[i] ] 等同于 f[ i - 1 ][ j - v[i] ],

        所以有,

           ②:f [ j ] = f[ j - v[i] ] + w[ i ]

      最后,f [ i ][ j ] = max(①, ②)

在写代码时,可以写成下面这种比较简洁。

1         for (int i = 1; i <= n; i++)
2             for (int j = m; j >= v[i]; j--)
3                 f[j] = max(f[j], f[j - v[i]] + w[i]);

 

3,f[] 初始化不同引起的含义不同:

对于一维的代码:

  ① 初始化为:f[ j ] == 0;

    此时 f[ j ] 代表如果当前考虑的是第 i 个物品,那么在总体积小于等于 j 的情况下,只考虑到前 i 个物品的背包的最大价值是 f[ j ]。

  ② 初始化为:f[ 0 ] == 0;f[ j ] == -inf;

    此时 f[ j ] 代表如果当前考虑的是第 i 个物品,那么在总体积恰好等于 j 的情况下,只考虑到前 i 个物品的背包的最大价值是 f[ j ]。

  证 :

    如果是 ①

      Ⅰ 我们可以从 f[ 0 ] == 0,根据 f[ v[i] ] = max(f[ 0 ], w[i]) ,推导出所有 f[ j ]。

      Ⅱ 设有 k <= j <= m,因为初始时 f[ k ] == 0,

        我们也可以从 f[ k ] == 0,根据 f[ k + v[i] ] = max(f[ k ], w[i]) ,推导出所有 f[ j ]。

      即,f[ j ] 可能是由 f[ 0 ] 推导出来的,则此时 f[ j ] 所需的体积必然等于 j。

      但,f[ j ] 也可能是由 f[ k ] 推导出来的,即此时 f[ j ] 所需的体积为 j - k。

      综上,f[ j ] 所需的体积小于等于 j。所以 f[ m ] 就是最大值。

    如果是 ②

      则会因为 f[ k ] == -inf,使得 ① 中的 Ⅱ 的情况不会发生,使得 f[ j ] 所需的体积一定是恰好等于 j。

      所以,f[ m ] 不可能包含所有的情况,那么就必须比较所有 f[ j ] 得到最大值才是答案。

下面的代码中,给出了一个 ② 情况下,f[ m ] 不等于最大值的例子,可运行看看。

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#define inf 0x3f3f3f3f
#define N 35
#define M 125
int v[N], w[N]; // 物品体积,物品价值
int f[M];
int max(int a, int b)
{
    return a > b ? a : b;
}
int main(void)
{
    /*
    测试案例:
    2 2
    1 101
    2 100
    */
    int n, m;      // 物品数,背包体积
    while (scanf("%d%d", &n, &m) != EOF)
    {
        for (int i = 1; i <= m; i++)
            f[i] = -inf;
        for (int i = 1; i <= n; i++)
            scanf("%d%d", &v[i], &w[i]);

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

        for (int i = 1; i <= m; i++)
            printf("%d ", f[i]);
        puts("");
    }
    return 0;
}
View Code

对于二维的代码:

  初始化只有一种情况,即

     f[ 0 ][ j ] == 0,对应一维的 f[ j ] == 0。

  因为二维的所有值都是由 f[ 0 ][ j ] 推导而来的,所以 f[ i ][ j ] 代表的是:

    只考虑到前 i 个物品,且总体积恰好等于 j 的情况下,背包的最大价值。

  所以,最后必须比较所有 f[ n ][ j ] 得到最大值才是答案。

 

4,例题:

链接:

  http://poj.org/problem?id=3624

二维代码:内存超限

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define N 3500
#define M 12880+5
int f[N][M];
int v[N], w[N]; // 物品体积,物品价值
int max(int a, int b)
{
    return a > b ? a : b;
}
int main(void)
{
    int n, m;    // 物品数,背包体积
    while (scanf("%d%d", &n, &m) != EOF)
    {
        memset(f, 0, sizeof(f));
        for (int i = 1; i <= n; i++)
            scanf("%d%d", &v[i], &w[i]);

        for (int i = 1; i <= n; i++)
        {
            for (int j = 1; j <= m; j++)
            {
                f[i][j] = f[i - 1][j]; // 默认不选
                if (v[i] <= j)         // 如果可以选,即物品体积 <= 背包体积
                    f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
            }
        }
        int rs = 0;
        for (int i = 1; i <= m; i++)
            rs = max(rs, f[n][i]);
        printf("%d\n", rs);
    }
    return 0;
}
/*
4 6
1 4
2 6
3 12
2 7
答案:23
*/
View Code

一维代码

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define N 3500
#define M 12880+5
int v[N], w[N]; // 物品体积,物品价值
int f[M];
int max(int a, int b)
{
    return a > b ? a : b;
}
int main(void)
{
    int n, m;    // 物品数,背包体积
    while (scanf("%d%d", &n, &m) != EOF)
    {
        memset(f, 0, sizeof(f));
        for (int i = 1; i <= n; i++)
            scanf("%d%d", &v[i], &w[i]);

        for (int i = 1; i <= n; i++)
            for (int j = m; j >= v[i]; j--)
                f[j] = max(f[j], f[j - v[i]] + w[i]);
        printf("%d\n", f[m]);
    }
    return 0;
}
/*
4 6
1 4
2 6
3 12
2 7
答案:23
*/
View Code

 

 

 

二,完全背包问题

三,多重背包问题

四,混合背包问题

五,二维费用的背包问题

六,分组背包问题

七,背包问题求方案数

八,求背包问题的方案

九,有依赖的背包问题

 

========== ========= ======== ====== ====== ===== ==== === == =

  Do you think, because I am poor, obscure, plain, and little,I am soulless and heartless? 

  You think wrong! 

  I have as much soul as you,  and full as much heart!

  And if God had gifted me with some beauty and much wealth, I should have made it as hard for you to leave me, as it is now for me to leave you.  

  I am not talking to you now through the medium of custom, conventionalities, nor even of mortal flesh;

  it is my spirit that addresses your spirit,just as if both had passed through the grave, and we stood at God's feet, equal, -- as we are!       

                                                —— —— Jane Eyre

 

posted @ 2021-07-21 21:50  叫我妖道  阅读(121)  评论(0编辑  收藏  举报
~~加载中~~