AcWing 1020. 潜水员

题目传送门

一、与普通二维背包费用问题的区别

  • 普通的二维背包费用问题
    \(f(i,j,k)\) 表示从前\(i\)个物品中选,且花费\(1\)不超过\(j\),花费\(2\)不超过\(k\)最大价值

  • 潜水员
    \(f(i,j,k)\) 表示从前\(i\)个物品中选,且花费\(1\)至少为\(j\),花费\(2\)至少为\(k\)最小价值

二、闫氏DP分析法

这样分析完,似乎与普通的二维费用背包没有区别!!!这肯定是不可能的,我们必然是遗漏了些什么!!

需要考虑\(j-v1<0\),\(k-v2<0\)的情况

有普通的二维费用背包问题中,\(j,k\)是不能进行超载的,超过了背包就了!

在本题中,是可以超载的,因为我们可以根据题意理解一下超载了是什么意思:
其实就是\(v1>j\),\(v2>k\),用人话说就是当前气缸提供的氧气或者当前气缸提供的氮气已经足够满足题目要求!如果是这样的情况,那么不需要考虑之前什么样,自己本身就达标了,不用从前面的递推过来!

此时,不是从\(f[i-1,j-v_1,k-v_2]+w\)转移过来,而是直接从\(f[0,0,0]+w\)来就行了。

三、三维朴素解法

#include <bits/stdc++.h>

using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 1010;
const int M = 85;
int f[N][M][M];//选择前i个气缸,且花费1不小于j,花费2不小于k的最小重量
int v1[N], v2[N], w[N];
int n, V1, V2;

//https://www.bilibili.com/video/BV1u44y147NJ?from=search&seid=13349588448379371452&spm_id_from=333.337.0.0
int main() {
    //注意一下这个录入的次序
    cin >> V1 >> V2 >> n;
    //至少
    memset(f, 0x3f, sizeof f);
    f[0][0][0] = 0;

    for (int i = 1; i <= n; i++)cin >> v1[i] >> v2[i] >> w[i];
    for (int i = 1; i <= n; i++)
        for (int j = 0; j <= V1; j++)
            for (int k = 0; k <= V2; k++) {
                f[i][j][k] = f[i - 1][j][k];
                f[i][j][k] = min(f[i][j][k], f[i - 1][max(j - v1[i], 0)][max(k - v2[i], 0)] + w[i]);
            }
    printf("%d", f[n][V1][V2]);
    return 0;
}

四、降到二维解法

本题的降二维和普通的二维费用背包也有细节上的区别,比如普通版本的二维费用背包:

for (int i = 1; i <= n; i++)
  for (int j = V; j >= v1[i]; j--)     //由大到小遍历每一个体积
     for (int k = M; k >= v2[i]; k--) //由大到小遍历每一个重量
          f[j][k] = max(f[j - v1[i]][k - v2[i]] + w[i], f[j][k]);//动态转移方程

本题的代码如下:

for (int i = 1; i <= n; i++)
  for (int j = V1; j >= 0; j--)
     for (int k = V2; k >= 0; k--)
          f[j][k] = min(f[j][k], f[max(j - v1[i], 0)][max(k - v2[i], 0)] + w[i]);

同学们可以看一下,除了状态转移方程存在上面讨论的问题、修改过之外,在\(j,k\)的遍历范围上也存在的差异!我们来看一下是为什么:

  • 普通二维费用背包
    剩余空间、剩余重量,不得小于当前物品占用空间和重量,否则装不下了,如果遍历到\(0\),下面的状态转移方程f[j - v1[i]][k - v2[i]] + w[i]就会出现负数,负数在数组中就是下标越界,无意义了,所以只能到达 \(v1[i],v2[i]\)

  • 本题
    本题是允许空间1和空间2出现负数的,代表的含义是:需要\(j\)升氧气,现在第\(i\)个物品就能提供\(v1[i]\)升,\(v1[i]>j\),也就是\(j-v1[i]<0\),就在现实世界中是符合逻辑的,是对的,被允许的。这个状态不需要从前序状态转移而来,自己足够!如果也限定了\(V1,V2\)做为上限,就丢失了这类情况!

#include <bits/stdc++.h>

using namespace std;
const int N = 1010; //气缸的个数上限
const int M = 100;  //需要的氧气、氮气上限值

int V1; //氧需要的量
int V2; //氮需要的量
int n;  //气缸的个数

int v1[N], v2[N], w[N];//第i个气缸里的氧和氮的容量及气缸重量
int f[M][M]; //DP数组

int main() {
    //读入氧需要的量,氮需要的量,气缸的个数
    cin >> V1 >> V2 >> n;
    //读入每个气缸的氧和氮的容量及气缸重量
    for (int i = 1; i <= n; i++) cin >> v1[i] >> v2[i] >> w[i];
    //求最小值要把除初始状态以外的所有状态初始化为+∞
    memset(f, 0x3f, sizeof f);
    f[0][0] = 0;
    //因为采用了降维,所以体积都是倒着的~
    for (int i = 1; i <= n; i++)
        for (int j = V1; j >= 0; j--)
            for (int k = V2; k >= 0; k--)
                f[j][k] = min(f[j][k], f[max(j - v1[i], 0)][max(k - v2[i], 0)] + w[i]);
    //输出
    printf("%d", f[V1][V2]);
    return 0;
}
原文地址:https://www.cnblogs.com/littlehb/p/15689906.html