装箱子(c++)(物品价值很大的情况)

文章介绍了如何使用动态规划解决背包问题,包括二维DP方法、一维空间优化的DP解法以及深度优先搜索(DFS)策略。二维DP方法虽然直观但空间复杂度过高,一维优化后的DP解法降低了空间复杂度,而DFS提供了另一种思路,但其时间复杂度较高。
摘要由CSDN通过智能技术生成

现在小明有一个超大容量的箱子。有n个具有容量v,价值w的物品,他想要装下物品容量和不超过

箱子的前提下,使得总价值最大。你能帮他解决这个问题吗?请你输出这个最大值

输入格式

第一行一个正整数n

接下来n行,每行两个正整数v,w

输出格式

一行,一个正整数表示最大总价值

样例输入
5 20
8 87
7 10
2 10
15 76
13 45
样例输出
107

这是一个经典的dp问题,首先想到的是用dp思想来解决

第一种方法二维dp

  1. 状态f[i][j]定义:前i个物品,背包容量g下的最优解(最大价值)

当前的状态依赖于之前的状态,可以理解为从初始状态f[0][0] = 0开始决策,有 N

件物品,则需要 N次决 策,每一次对第 i件物品的决策,状态f[i][j]不断由之前的状态更新而来。

2.当前背包容量不够(j < v[i]),没得选,因此前 i个物品最优解即为前 i−1 个物品最优解:

对应代码:f[i][j] = f[i - 1][j]。

3.当前背包容量够,可以选,因此需要决策选与不选第 i个物品:

选:f[i][j] = f[i - 1][j - v[i]] + w[i]。

不选:f[i][j] = f[i - 1][j] 。

我们的决策是如何取到最大价值,因此以上两种情况取 max() 。

代码如下

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 100;
const int NN = 100010;
int v[N];
int w[NN];
int dp[N][NN];
int main() {
    int n, m;
    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 = 1; j <= m; j++) {
            if (j < v[i]) {
                dp[i][j] = dp[i - 1][j];
            }
            else {
                dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - v[i]] + w[i]);
            }
        }
    }
    cout << dp[n][m] << endl;
    return 0;
}

此种方法时间复杂度和空间复杂度维nV,会超时,因此只能过60%的数据


第二种解法

我们可以对二维数组进行优化为一维数组

将状态f[i][j]优化到一维f[j],实际上只需要做一个等价变形。

为什么可以这样变形呢?我们定义的状态f[i][j]可以求得任意合法的i与j最优解,但题目只需要求得最终状态f[n][m],因此我们只需要一维的空间来更新状态。

(1)状态f[j]定义:N件物品,背包容量j下的最优解。

(2)注意枚举背包容量j必须从m开始。

(3)为什么一维情况下枚举背包容量需要逆序?在二维情况下,状态f[i][j]是由上一轮i - 1的状态得来的,f[i][j]与f[i - 1][j]是独立的。而优化到一维后,如果我们还是正序,则有f[较小体积]更新到f[较大体积],则有可能本应该用第i-1轮的状态却用的是第i轮的状态。

(4)例如,一维状态第i轮对体积为3的物品进行决策,则f[7]由f[4]更新而来,这里的f[4]正确应该是f[i - 1][4],但从小到大枚举j这里的f[4]在第i轮计算却变成了f[i][4]。当逆序枚举背包容量j时,我们求f[7]同样由f[4]更新,但由于是逆序,这里的f[4]还没有在第i轮计算,所以此时实际计算的f[4]仍然是f[i - 1][4]。

(5)简单来说,一维情况正序更新状态f[j]需要用到前面计算的状态已经被「污染」,逆序则不会有这样的问题。

状态转移方程为:f[j] = max(f[j], f[j - v[i]] + w[i] 。

代码如下

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int n, m;
    cin >> n >> m;
    vector<int>f(m + 1);
    for (int i = 1; i <= n; i++) {
        int v, w;
        cin >> v >> w;      // 边输入边处理
        for (int j = m; j >= v; j--)
            f[j] = max(f[j], f[j - v] + w);
    }
    cout << f[m] << endl;
    return 0;
}

此种方法时间和空间复杂度都为V,但空间复杂度8*10的七次方换算后大约为134MB,大于题目所给的空间。只能过90%的数据

第三种解法可以用DFS

定义一个backtrack函数接收三个参数:

1.idx表示当前考虑到第几个物品,cur_v表示当前已经装载的物品容量,cur_w表示当前已经获得的总价值。

2.对于每个物品,我们可以选择不装载或者装载,然后递归调用backtrack函数,继续考虑下一个物品。

3.当考虑完所有物品之后,如果当前已经装载的物品容量不超过箱子容量V,那么更新最大价值。最后输出最大价值即可。

代码如下

#include <iostream>
#include <vector>

using namespace std;

int n, V;
vector<int> v, w;
int ans = 0;

void backtrack(int idx, int cur_v, int cur_w) {
    if (idx == n) {
        if (cur_v <= V) {
            ans = max(ans, cur_w);
        }
        return;
    }
    backtrack(idx + 1, cur_v + v[idx], cur_w + w[idx]);//选当前物品
    backtrack(idx + 1, cur_v, cur_w); // 不选当前物品
   
}

int main() {
    cin >> n >> V;
    v.resize(n);
    w.resize(n);
    for (int i = 0; i < n; i++) {
        cin >> v[i] >> w[i];
    }
    backtrack(0, 0, 0);
    cout << ans << endl;
    return 0;
}

时间复杂度为2的n次方空间复杂度为1;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值