dp背包
本文介绍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[i−1][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[i−1][j−w[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[i−1][j],dp[i−1][j−w[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[i−1][j],f[i][j−w]+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;
}
后序会更新相关题型题目,来个赞鼓励下!