• 动态规划:背包问题


    01背包

    题面

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

    暴力枚举

    枚举:我们可以用一个01串表示每个物品取还是不取,对于每一个串,我们取总体积小于等于m的方案,然后取总收益最大的

    动态规划

    当我们在考虑了前 i i i个物品取或不取,如果有两组方案的总体积相同,那么我们只需要保留总收益大的那组。

    考虑前 i i i个物品,总体积为 j j j时分为两种情况:

    i i i个物品没取,问题变成考虑前 i − 1 i-1 i1个物品,总体积为 j j j时的情况

    i i i个物品取了,问题变成考虑前 i − 1 i-1 i1个物品,总体积为 j − v i j-v_i jvi时的情况

    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]);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    滚动数组优化(空间优化)

    考虑前 i i i个物品时的状态只和考虑前 i − 1 i-1 i1个物品时状态有关,前 i − 2 i-2 i2都不用记。

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    思路优化

    我们将f数组和g数组合并可以发现,在j < v[i]的时候g[j] = f[j],那么我们只需要从后向前逐渐更新f数组,就能将f数组更新成g数组(如果从前向后,可能会使后面更新所需要的旧值已经被更新成新的值,那么边会发生错误)。

    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]);
    
    • 1
    • 2
    • 3

    完全背包

    题面

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

    思路

    把考虑了前 i i i种物品以后,总体积为0,1,2,3,……,m时的最大收益记录下来。

    考虑前 i i i个物品,总体积为 j j j时分为两种情况:

    i i i个物品没取,问题变成考虑前 i − 1 i-1 i1个物品,总体积为 j j j时的情况

    i i i个物品取了,问题变成考虑前 i i i个物品,总体积为 j − v i j-v_i jvi时的情况

    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][j - v[i]] + w[i]);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    思路优化

    在更新f[i][j]的时候,f[i][j] = max(f[i-1][j],f[i][j-v[i]]+w[i]),我们需要用到f[i][j-v[i]]也就是更新过后的f[j-v[i]],所以需要从前向后更新(如果从后向前更新,有可能用到还没有进行更新的数据也就是f[i-1][j-v[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]);
    
    • 1
    • 2
    • 3

    恰好装满的完全背包问题

    memset(f, 128, sizeof f);     //将f数组初始化成负无限大
    f[0] = 0;
    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]);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    多重背包

    题面

    n n n种物品要放到一个袋子里,袋子的总容量为 m m m,第 i i i种物品的体积为 v i v_i vi,把它放进袋子里会获得 w i w_i wi的收益,可以用 l i l_i li次。问如何选择物品,使得在物品的总体积不超过 m m m的情况下,获得最大的收益?请求出最大收益。

    数据规模小

    可以将一个物品拆分成 l i l_i li个物品,每个物品只能拿一次,转换成01背包问题解决。

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

    数据规模大

    定理:令 t 为最大的 i 满足 2 i + 1 − 1 ≤ n 2^{i+1}−1≤n 2i+11n,我们从 1 , 2 , 4 , . . . , 2 t , n − 2 t + 1 + 1 1,2,4,...,2^t,n−2^{t+1}+1 1,2,4,...,2t,n2t+1+1 中选一些数字相加(可以不选),可以得出任意 [0,n] 内的值,每个数字只能用一次。

    对于第 i i i 种物品,它可以取 l i l_i li 次,我们可以按上述定理的方法把它拆成 O ( l o g l ) O(log l) O(logl) 个物品,每个物品是取若干次第 i i i 种物品的打包,每个打包完的物品只能取一次。从这 O ( l o g l ) O(log l) O(logl) 个物品里选一些,可以表示出第 i i i种物品不取,取 1 次,取 2 次,…,取 l i l_i li 次所有这些情况。

    for (int i = 1; i <= n; i++)
    {
        int res = l[i];
        for (int k = 1; k <= res; res -= k, k *= 2)
            for (int j = m; j >= v[i] * k; j--)
                f[j] = max(f[j], f[j - v[i] * k] + w[i] * k);
        for (int j = m; j >= v[i] * res; j--)
            f[j] = max(f[j], f[j - v[i] * res] + w[i] * res)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    单调队列

    单调队列优化

    时间复杂度: O ( n m l ) O(nml) O(nml)

    i i i固定的情况下,我们把 j j j按照 分类,只有同一类的状态间可以进行转移;

    在第 i − 1 i - 1 i1行的某一类中下标为 x x x的位置 p p p,可以转移到第 i i i 行下标在 [ x + 1 , x + l i ] [x+1,x+l_i] [x+1,x+li]中的位置;

    下标为 x x x的位置会在 x + l i x+l_i x+li时刻消失,每个位置都要取最大值;

    我们要转移到下标为y的位置o,对应的影响是 f [ i − 1 ] [ p ] + ( y − x ) ∗ w i f[i-1][p]+(y-x)*w_i f[i1][p]+(yx)wi,展开式子得 f [ i − 1 ] [ p ] − x ∗ w i + y ∗ w i f[i-1][p]-x*w_i+y*w_i f[i1][p]xwi+ywi,其中 y ∗ w i y*w_i ywi这项只和要转移到的位置的下标 y y y有关,与 x x x没有任何关系,我们只要在算 f [ i ] [ o ] f[i][o] f[i][o]的时候把它加上去就好了。剩下的 f [ i − 1 ] [ p ] − x ∗ w i f[i-1][p]-x*w_i f[i1][p]xwi x x x, p p p固定的时候是一个定值。

    #include 
    using namespace std;
    int n, m, v, w, t, f[10001], c[10001][2];
    int main()
    {
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= n; i++)
        {
            scanf("%d%d%d", &v, &w, &t); // t是题目中的l,为了防止和队首位置的变量名重复,所以用t来表示
            for (int j = 0; j < v; j++) // j枚举了mod vi后是第几个分类。
            {
                int k = 0, l = 1;
                for (int p = j, x = 1; p <= m; p += v, ++x) //依次枚举第j类的位置p和它的下标x
                {
                    int e = f[p] - x * w, r = x + t;
                    while (k >= l && c[k][0] <= e)
                        --k;
                    c[++k][0] = e;
                    c[k][1] = r;
                    f[p] = c[l][0] + x * w;
                    while (k >= l && c[l][1] == x)
                        ++l;
                }
            }
        }
        printf("%d\n", f[m]);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    分组背包

    题面

    n n n种物品要放到一个袋子里,袋子的总容量为 m m m。第 i i i个物品属于第 a i a_i ai组,每组物品我们只能从中选择一个。第i种物品的体积为 v i v_i vi,把它放进袋子里会获得 w i w_i wi的收益。问如何选择物品,使得在物品的总体积不超过 m m m的情况下,获得最大的收益?请求出最大收益。

    思路

    让我们来看看大概的思路,我们把考虑了前 i 组物品(编号小于等于 i 的组)以后,总体积为 0,1,…,m 时的最大收益都记下来

    考虑了前 i 组物品,总体积为 j 时分为两种情况:

    i i i组物品一个没取,问题变成了考虑了前 i − 1 i−1 i1组物品,总体积为 j j j时的情况;

    i i i组物品取了,我们枚举取了其中的物品 k k k,问题变成了考虑了前 i − 1 i−1 i1组物品,总体积为 j − v k j−v_k jvk 时的情况;

    int n, m, v[N], w[N], a[N], f[N][N];
    vector c[N];
    
    for (int i = 1; i <= 1000; i++)
        for (int j = 0; j <= m; j++)
        {
            f[i][j] = f[i - 1][j];
            for (auto k : c[i])
                if (v[k] <= j)
                    f[i][j] = max(f[i][j], f[i - 1][j - v[k]] + w[k]);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    优化

    同01背包优化

    int n, m, v[N], w[N], a[N], f[N];
    vector c[N];
    
    for (int i = 1; i <= 1000; i++)
        for (int j = m; j; j--)
            for (auto k : c[i])
                if (v[k] <= j)
                    f[j] = max(f[j], f[j - v[k]] + w[k]);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    二维背包

    题面

    n n n种物品要放到一个袋子里,袋子的总容量为 m m m,我们一共有k点体力值。第 i i i种物品的体积为 v i v_i vi,把它放进袋子里会获得 w i w_i wi的收益,并且消耗我们 t i t_i ti点体力值,每种物品只能取一次。问如何选择物品,使得在物品的总体积不超过 m m m并且花费总体力不超过 k k k的情况下,获得最大的收益?请求出最大收益。

    思路

    把考虑了前 i i i个物品以后,总体积为 0 , 1 , … , m 0,1,…,m 0,1,,m,总体力为 0 , 1 , … , k 0,1,…,k 0,1,,k时的最大收益都记下来。

    考虑了前 i i i个物品,总体积为 j j j,总体力为 x x x时分为两种情况:

    i i i个物品没取,问题变成了考虑了前 i − 1 i−1 i1个物品,总体积为 j j j,总体力为 x x x时的情况;

    i i i个物品取了,问题变成了考虑了前 i − 1 i−1 i1个物品,总体积为 j − v i j−v_i jvi,总体力为 x − t i x−t_i xti时的情况;

    for (int i = 1; i <= n; i++)
        for (int j = 0; j <= m; j++)
            for (int x = 0; x <= k; x++)
            {
                f[i][j][x] = f[i - 1][j][x];
                if (j >= v[i] && x >= t[i])
                    f[i][j][x] = max(f[i][j][x], f[i - 1][j - v[i]][x - t[i]] + w[i]);
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    优化

    同01背包优化

    for (int i = 1; i <= n; i++)
        for (int j = m; j >= v[i]; j--)
            for (int x = k; x >= t[i]; x--)
                f[j][x] = max(f[j][x], f[j - v[i]][x - t[i]] + w[i]);
    
    • 1
    • 2
    • 3
    • 4
  • 相关阅读:
    操作系统安装在哪里?
    某验三代滑块流程分析
    基于session推荐的论文阅读
    C++中的类详解
    react antd下拉选择框选项内容换行
    golang的垃圾回收算法之十写屏障的技术
    (C)一些错题
    java计算机毕业设计ssm企业日常事务管理系统sl5xl(附源码、数据库)
    分享自己平时使用的socket多客户端通信的代码技术点和软件使用
    SpringBoot——整合Mongodb
  • 原文地址:https://blog.csdn.net/qq_61540355/article/details/126202765