0-1背包问题系列

1.背包问题
问题描述:

有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。

第 i 件物品的体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。

接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。

输出格式
输出一个整数,表示最大价值。

数据范围
0<N,V≤1000
0<vi,wi≤1000

这算是我第一次接触背包问题,按我以往的思路,要不是先写回溯的过程,要不直接写动态规划,这次这个和之前的不太一样,因为它的有两部分决定的动态规划方程,只想一个会发现找不到递推关系。eg:我们规定dp[i]为第i个物品加入的最大值,那体积不同是否是就不一定了,所以我们要用二维数组解决问题。
思路: dp[i][j]表示前i个物品在容积为j时的最大值。
这样一来,转移方程为:
dp[i][j] = max(dp[i-1][j], dp[i-1][j-v[i]] + w[i]); (j > v[i]时)
dp[i][j = dp[i-1][j];(j < v[i]时)

意思就是:如果当前这个物品比当前的容量还大那这件物品就不可能装进来了,那么价值就等于容量在j时前i-1个物品的价值;如果当前物品可以装进来,那么我们需要决策:即装入还是不装入当前物品?若装入,则需要预留出这个物品所需要的容量,那么价值最大值为:当前容量减去当前物品的体积的条件下的价值+当前物品的价值;若不装入,则同第一种情况,等于当前容积下前i-1个物品的价值。
最开始用的回溯:

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int dfs(vector<int> v, vector<int> w, int cap, int value, int index) {
    if(cap < 0) {
        //cout << "第" << index-1 << "物品放不下了,最大值为" << value-w[index-1] << endl;
        return value - w[index-1];
    }
    if(index == v.size()) {
        //cout << "遍历完成,本次尝试最大值为" << value << endl;
        return value;
    }
    
    int res = 0;
    for(int i = index; i < v.size(); i++) {
        //cout << "将第" << i << "物品加入背包中" << endl;
        res = max(res, dfs(v, w, cap-v[i], value+w[i], i+1));
        //cout << "将第" << i << "物品取出,尝试下一个物品" << endl;
    }
    return res;
}

int main() {
    int n, cap;
    vector<int> v, w;
    cin >> n >> cap;
    for(int i = 0; i < n; i++) {
        int a, b;
        cin >> a >> b;
        v.push_back(a);
        w.push_back(b);
    }
    cout << dfs(v, w, cap, 0, 0) << endl;
    return 0;
}

接下来看了题解,发现是这样的思路解决的

#include <iostream>
#include <algorithm>

using namespace std;

const int c = 1001;

int dp[c][c];
int v[c], w[c];
    
int main() {
    int n, cap;
    cin >> n >> cap;
    for(int i = 1; i <= n; i++) {
        cin >> v[i] >> w[i];
    }
    
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= cap; j++) {
            if(j < v[i]) dp[i][j] = dp[i-1][j];
            else {
                dp[i][j] = max(dp[i-1][j], w[i]+dp[i-1][j-v[i]]);
            }
        }
    }
    cout << dp[n][cap] << endl;
    return 0;
}

还可以优化一下空间,用一维数组代替:可以发现我们每次用的就是上一步推出来的结果,我们可以一直用一维数组覆盖掉上一步的结果即可。唯一注意的是:我们要从后向前遍历,因为递推过程中我们需要前面的结果,如果我们还是从前向后遍历那么上一步的结果就被我们覆盖掉了。

#include <iostream>
#include <algorithm>

using namespace std;

const int c = 1001;

int dp[c];
int v[c], w[c];
    
int main() {
    int n, cap;
    cin >> n >> cap;
    for(int i = 1; i <= n; i++) {
        cin >> v[i] >> w[i];
    }
    
    for(int i = 1; i <= n; i++) {
        for(int j = cap; j >= 0; j--) {
            if(j >= v[i]) dp[j] = max(dp[j], w[i]+dp[j-v[i]]);
        }
    }
    
    cout << dp[cap] << endl;
    return 0;
}

2.完全背包问题
问题描述:

有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。

第 i 种物品的体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。

接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 种物品的体积和价值。

输出格式
输出一个整数,表示最大价值。

数据范围
0<N,V≤1000
0<vi,wi≤1000

就是上一道题的扩展,这次每件物品有无数个。
首先从上到下的思考问题就是:我们想知道当容量为V时,最大有多少价值?最大价值为:遍历所有物品,最大价值为当体积为“V-当前物品的体积”条件下能获取的最大值+当前物品的价值。 然后递归就可以得到答案了。最底层的问题就是当容积为0时所能获取的最大价值,即为0 。那么递归的代码就呼之欲出了;其次我们用记忆化搜索的方法记录当体积为i时所能获取的最大价值,即可ac。

#include <iostream>
#include <algorithm>

using namespace std;

const int cap = 1001;
int v[cap], w[cap];
int dp[cap];

int dfs(int V, int n) {
    if(V == 0) return 0;
    if(dp[V] != 0) return dp[V];
    
    int res  = 0;
    for(int i = 1; i <= n; i++) {
        if(v[i] <= V) res = max(res, w[i] + dfs(V-v[i], n));
    }
    dp[V] = res;
    return res;
}
int main() {
    int n, V;
    cin >> n >> V;
    for(int i = 1; i <= n; i++) {
        cin >> v[i] >> w[i];
    }
    
    cout << dfs(V, n) << endl;
    return 0;
}

正常来说知道了记忆化的过程,动态规划应该可以马上出来,好家伙,卡了我好一会,还是不够熟练的原因。我们要解决的底层问题就是当体积为0,1……的时候能获取的最大价值是多少;我们只要两个for,dp[i]等于本身和尝试加入满足条件的物品的最大值即可。(为什么要和本身比呢?因为这个本身有可能是上一次尝试加入新物品得到的值,和这一次相比,决出最大值即可)。

#include <iostream>
#include <algorithm>

using namespace std;

const int cap = 1001;
int v[cap], w[cap];
int dp[cap];

int main() {
    int n, V;
    cin >> n >> V;
    for(int i = 1; i <= n; i++) {
        cin >> v[i] >> w[i];
    }
    
    for(int i = 1; i <= V; i++) {
        for(int j = 1; j <= n; j++) {
            if(i >= v[j]) {
                dp[i] = max(dp[i], dp[i-v[j]]+w[j]);
            }
        }
    }
    cout << dp[V] << endl;
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值