dp背包(01,完全)

dp背包

本文介绍01背包、完全背包

  • 01背包:

​ 什么是 01 01 01背包,通俗点将有== n n n个物品,每个物品有自身的两个属性: w e i g h t weight weight v a l u e value value(重量和价值),现在你有一个背包,背包能承受的最大重量为t==,这n个物品有且仅有一个,你可以选择拿或者不拿,请问在不超错背包最大承重t的情况下,背包里面物品的价值总和最大是多少?

定义状态 d p [ i ] [ j ] dp[i][j] dp[i][j]:考虑了前i件物品,背包中物品总重量不超过j的最大价值。

状态转移:(考虑物品取或不取)

一:第i件物品没有放入背包,那 d p [ i ] [ j ] dp[i][j] dp[i][j]应该是考虑了前i-1件物品,背包总体积不超过j的最大价值(通俗点就是在没取第i件物品背包总承重不超过j,就是由前i-1件物品凑出的 d p [ i − 1 ] [ j ] dp[i-1][j] dp[i1][j]来更新 d p [ i ] [ j ] dp[i][j] dp[i][j]

二:第i件物品放入了背包,那 d p [ i ] [ j ] dp[i][j] dp[i][j]就是从 d p [ i − 1 ] [ j − w [ i ] ] + v [ i ] dp[i-1][j - w[i]] + v[i] dp[i1][jw[i]]+v[i]来更新,这里的w[i]是第i件物品的重量,需要满足j - w[i] > 0。

d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − w [ i ] ] + v [ i ] ) dp[i][j] = max(dp[i-1][j], dp[i-1][j - w[i]] + v[i]) dp[i][j]=max(dp[i1][j],dp[i1][jw[i]]+v[i])

#include<iostream>
#include<vector>
using namespace std;

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

当然这个代码还可以优化空间,每一次状态转移时,每次只用到了最近一次的状态,例如第i次的状态仅通过第i-1的状态转换过来,可以通过一个滚动数组进行

#include<iostream>
#include<vector>
using namespace std;

int main() {
    int t, n; cin >> t >> n;
    vector<int> w(n+1), v(n+1), g(t+1, 0), f(t+1, 0); 
    for(int i = 1; i <= n; i++) {
        cin >> w[i] >> v[i]; 
    }
    for(int i = 1; i <= n; i++) { 
        g = f; //
        for(int j = 0; j <= t; j++) {      
            if(j >= w[i]) f[j] = max(f[j], g[j - w[i]] + v[i]);
        }
    }
    cout << f[t];
    return 0;
}

最终版本可以直接不适用滚动数组,只用一个一维数组f来实现

#include<iostream>
#include<vector>
using namespace std;

int main() {
    int t, m; cin >> t >> m;
    vector<int> w(m+1), v(m+1), f(t+1);
    for(int i = 1; i <= m; i++) {
        cin >> w[i] >> v[i]; 
        for(int j = t; j >= w[i]; j--) { //注意这里是从大到小,这样f[j-w[i]]始终是i-1时候的,如果从小到大,就会相当于是已经更新过f[j-w[i]],相当与f[i][j-w[i]];与01状态转移方程不符和了,但是这个在完全背包里面可以,看到后面可以回过来看这里。
            f[j] = max(f[j], f[j - w[i]] + v[i]);
        }
    }
    cout << f[t];
    return 0;
}
  • 完全背包:

​ 在01背包的基础上, 现在每个物品的数量不只是1,而是无穷多,就是你现在可以每样物品拿任意个。

暴力思路就是考虑每一个物品,从0开始枚举其拿取个数,因为背包最大承重为t,所以虽然说可以无限拿,实际上每个物品最多拿 t/w[i] 个,所以我们可以暴力枚举每一个,还是像01背包做法一样。看代码:

#include<iostream>
#include<vector>
using namespace std;

int main() {
    int t, n; cin >> t >> n;
    vector<int> w(n+1), v(n+1);
    vector<vector<int>> f(n+1, vector<int>(t+1, 0));
    for(int i = 1; i <= n; i++) {
        cin >> w[i] >> v[i];
    }
    for(int i = 1; i <= n; i++) {
        for(int j = 0; j <= t; j++) {
            for(int k = 0; k * w[i] <= j; k++) { //这里就是对每一种可能进行枚举
                f[i][j] = max(f[i-1][j], f[i-1][j-k*w[i]] + k*v[i]);
            }
        }
    }
    cout << f[n][t];
    return 0;
}

但是对于稍微大一点的t就可能会超时,如何优化呢?

经过上面推导,可以得出优化后的状态转移:

f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j ] , f [ i ] [ j − w ] + v ) f[i][j] = max(f[i-1][j], f[i][j-w] + v) f[i][j]=max(f[i1][j],f[i][jw]+v)

#include<iostream>
#include<vector>
using namespace std;
using ll = long long;

int main() {
    int t, n; cin >> t >> n;
    vector<int> w(n+1), v(n+1);
    vector<vector<ll>> f(n+1, vector<ll>(t+1, 0));
    for(int i = 1; i <= n; i++) {
        cin >> w[i] >> v[i];
    }
    for(int i = 1; i <= n; i++) {
        for(int j = 0; j <= t; j++) {
            f[i][j] = f[i-1][j];
            if(j >= w[i]) {
                f[i][j] = max(f[i][j], f[i][j-w[i]] + v[i]);
            }
        }
    }
    cout << f[n][t];
    return 0;
}

但是,对于二维而言,t稍微大点,就会超内存,所以考虑像01背包一样优化成一维

主要是将01背包中的j的for循环倒过来

来个滚动数组版本的:

#include<iostream>
#include<vector>
using namespace std;
using ll = long long;

int main() {
    int t, n; cin >> t >> n;
    vector<int> w(n+1), v(n+1);
    vector<ll> g(t+1, 0), f(t+1, 0);
    for(int i = 1; i <= n; i++) {
        cin >> w[i] >> v[i];
    }
    for(int i = 1; i <= n; i++) {
        g = f;
        for(int j = w[i]; j <= t; j++) {
            f[j] = max(g[j], g[j-w[i]] + v[i]);
            g[j] = f[j];//这里确保g[j-w[i]]是f[i][j-w[i]]
        }
    }
    cout << f[t];
    return 0;
}

终极版本:

#include<iostream>
#include<vector>
using namespace std;
using ll = long long;

int main() {
    int t, n; cin >> t >> n;
    vector<int> w(n+1), v(n+1);
    vector<ll>f(t+1, 0);
    for(int i = 1; i <= n; i++) {
        cin >> w[i] >> v[i];
        for(int j = w[i]; j <= t; j++) {
            f[j] = max(f[j], f[j-w[i]]+v[i]);
        }
    }
    cout << f[t];
    return 0;
}

后序会更新相关题型题目,来个赞鼓励下!

  • 26
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

幻听嵩的留香

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值