动态规划问题之背包模型(18题)

Posted _luckylight

tags:

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

背包问题是动态规划问题的一大类型,下面我们对这个进行总结。
以 Acwing y中总结的 几个类型,我写了几个题解

应用知识点

  • 01背包、完全背包 空间压缩的写法
  • 多维费用的背包问题,以及状态的不同表示对复杂度的 影响
  • 完全背包问题的三种求解方法 O ( N M S ) , O ( N M l o g S ) , O ( N M ) O(NMS), O(NMlogS),O(NM) O(NMS),O(NMlogS),O(NM)
  • dp维度关系等于,小于等于,大于等于对初始化的影响(潜水员)
  • dp关键信息最小值,最大值,方案数对初始化的影响

一、采药

Acwing 题目链接
非常裸的 01 背包
在这里插入图片描述

#include <bits/stdc++.h>
using namespace std;

const int N = 1010;
int f[N][N];
int n, m;

int main() 
{
    cin >> m >> n;
    memset(f, 0, sizeof f);
    for (int i = 1; i <= n; i ++ ) {
        static int t, v;
        scanf("%d%d", &t, &v);
        for (int j = 0; j <= m; j ++ ) {
            f[i][j] = f[i - 1][j];
            if (j >= t) {
                f[i][j] = max(f[i][j], f[i - 1][j - t] + v);
            }
        }
    }
    
    cout << f[n][m] << endl;
    
    return 0;
}

二、装箱问题

ACwing 题目链接
在这里插入图片描述
对于该问题,我给出两个解法(其实都类似 )


第一种
f[i][j] 表示利用前 i 个箱子,体积恰好为 j 的方案是否可行,是一个 bool 数组
那么 f [ i ] [ j ] = f [ i − 1 ] [ j ] ∣ f [ i − 1 ] [ j − v [ i ] ] f[i][j] = f[i-1][j] | f[i-1][j - v[i]] f[i][j]=f[i1][j]f[i1][jv[i]]
初始化时, f [ 0 ] [ 0 ] f[0][0] f[0][0] 置为 true,其余置为 false
下面是进行空间优化的写法

#include <bits/stdc++.h>
using namespace std;

const int N = 40, M = 40010;
int f[M];
int n, m;
int cost[N];
// f[i][j] 表示前 i个物品,体积 恰好等于 j 是否可以
// 然后进行状态压缩
int main()
{
    // input
    cin >> m >> n;
    for (int i = 1; i <= n; i ++ )
        scanf("%d", &cost[i]);
    
    f[0] = true;
    for (int i = 1; i <= n; i ++ ) {
        for (int j = m; j >= cost[i]; j -- ) {
            f[j] |= f[j - cost[i]];
        }
    }
    
    for (int i = m; ; i -- ) {
        if (f[i]) {
            cout << m - i << endl;
            break;
        }
    }
    
    return 0;
}


第二种写法
f [ i ] [ j ] f[i][j] f[i][j]表示的是对前 i 个箱子,体积小于等于 j 时候的最大体积
f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j ] , f [ i − 1 ] [ j − v [ i ] ] + v [ i ] f[i][j] =max(f[i-1][j], f[i-1][j-v[i]] + v[i] f[i][j]=max(f[i1][j],f[i1][jv[i]]+v[i]
这样的话,初始化全部为 0
而且最后输出结果也比较简单
m − f [ n ] [ m ] m - f[n][m] mf[n][m]
下面给出空间优化之后的代码

#include <bits/stdc++.h>
using namespace std;

const int N = 40010;
int f[N];
int n, m;
// f[i][j] 表示前 i 个物品,体积小于等于 j 时候可以容纳的最大体积
// 然后进行状态压缩

int main()
{
    cin>>m>>n;
    int v;
    memset(f, 0, sizeof f);
    for(int i = 1; i <= n; i++){
        cin>>v;
        for(int j = m; j >= v; j--){
            f[j] = max(f[j], f[j-v]+v);
        }
    }
    cout<<m-f[m]<<endl;
    return 0;
}

三、宠物小精灵值收服

Acwing 题目链接
在这里插入图片描述
在这里插入图片描述
本题是一个二维费用的 01 背包,理解起来不拿,不过题目属实有点长,而且 皮卡丘 的血量为 0 也会抓取失败,这是一个比较坑的点
下面,我给出两个dp的解决方案


方案一
f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k] 表示对前 i 个怪兽进行遍历,消耗精灵球数量小于等于j,消耗血量小于等于 k,的最大抓捕量
f [ i ] [ j ] [ k ] = m a x ( f [ i − 1 ] [ k ] [ k ] , f [ i − 1 ] [ j − c o s t 1 i ] [ k − c o s t 2 i ] ) f[i][j][k] = max(f[i-1][k][k], f[i-1][j-cost1_i][k-cost2_i]) f[i][j][k]=max(f[i1][k][k],f[i1][jcost1i][kcost2i])
对应的代码如下

#include <bits/stdc++.h>
using namespace std;

const int N = 1010, M = 510, K = 110, INF = 0x3f3f3f3f;
int cost1[K], cost2[K], n, m, k;
int f[N][M];

int main()
{
    // input
    cin >> n >> m >> k;
    m --;
    for (int i = 1; i <= k; i ++ ) {
        scanf("%d%d", &cost1[i], &cost2[i]);
    }
    
    for (int i = 1; i <= k; i ++ ) {
        for (int j = n; j >= cost1[i]; j -- ) {
            for (int k = m; k >= cost2[i]; k -- ) {
                f[j][k] = max(f[j][k], f[j - cost1[i]][k - cost2[i]] + 1);
            }
        }
    }
    
    int ans1, ans2;
    ans1 = f[n][m];
    for (int j = m; ; j -- ) {
        if (ans1 == 0) {
            ans2 = 0;
            break;
        }
        if (f[n][j] == ans1) {
            ans2 = j;
        } else {
            break;
        }
    }
    
    printf("%d %d\\n", ans1, m - ans2 + 1);
    
    return 0;
}

但是我们将复杂度考虑进去的话, O ( K N M ) O(KNM) O(KNM)有时候会过大,万一被卡怎么办? 给出方案二

方案二
f [ i ] [ M ] [ K ] f[i][M][K] f[i][M][K] 遍历前面i个精灵,统计的是在 体力恰好为 m, 捕捉恰好为 k 时候的最小消耗求的数量
复杂度 O ( K K M ) O(KKM) O(KKM)快了那么一点点
f [ i ] [ [ j ] [ k ] = m i n ( f [ i − 1 ] [ j ] [ k ] , f [ i − 1 ] [ m − c o s t 2 i ] [ k − 1 ] + c o s t 1 i ) f[i][[j][k]=min(f[i-1][j][k], f[i-1][m-cost2_i][k-1]+cost1_i) f[i][[j][k]=min(f[i1][j][k],f[i1][mcost2i][k1]+cost1i)
代码如下

#include <bits/stdc++.h>
using namespace std;

const int N = 1010, M = 510, K = 110, INF = 0x3f3f3f3f;
int n, m, k1;
int cost1[K], cost2[N];
int f[M][K];    // 统计的是在 体力恰好为 m, 捕捉恰好为 k 时候的最小消耗求的数量

int main() 
{
    // input
    cin >> n >> m >> k1;
    for (int i = 1; i <= k1; i ++ ) {
        scanf("%d%d", &cost1[i], &cost2[i]);
    }
    
    memset(f, 0x3f, sizeof f); f[0][0] = 0;
    for (int i = 1; i <= k1; i ++ ) {
        for (int j = m - 1; j >= cost2[i]; j -- ) {
            for (int k = k1; k >= 1; k -- ) {
                f[j][k] = min(f[j][k], f[j - cost2[i]][k - 1] + cost1[i]);
            }
        }
    }
    
    int ans1 = 0, ans2 = 0;
    for (int j = 0; j <= m - 1; j ++ ) {
        for (int k = 0; k < k1; k ++) {
            if (f[j][k] <= n) {
                if (k > ans1) {
                    ans1 = k, ans2 = j;
                } else if (f[j][k] == n && j < ans2) {
                    ans2 = j;
                }
            }
        }
    }
    
    printf("%d %d\\n", ans1, m - ans2);
    以上是关于动态规划问题之背包模型(18题)的主要内容,如果未能解决你的问题,请参考以下文章

动态规划问题之背包模型(18题)

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

动态规划之背包问题实现php

动态规划之背包问题(持续更新)

动态规划-第二节:动态规划之背包类型问题

java实现动态规划之背包问题