0-1 knapsack problem
已知一个背包最多能容纳体积之和为V的物品,现有 n 个物品,第 i 个物品的体积为 vi , 重量为 wi,求当前背包最多能装多大重量的物品?
使用动态规划来解决01背包问题:
首先定义dp数组:dp[i][j] := 0-i号物品面对容量为j的背包所能获得的最大重量
状态转移方程:
d p [ i ] [ j ] = { d p [ i − 1 ] [ j ] , 容量不足,不放入物品 i m a x ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − v [ i ] ] + w [ i ] ) , 考虑拿这件物品能否获得更大重量 dp[i][j]= \begin{cases} dp[i-1][j],\quad 容量不足,不放入物品i\\ max(dp[i - 1][j], dp[i - 1][ j - v[i]] + w[i]), \quad 考虑拿这件物品能否获得更大重量 \end{cases} dp[i][j]={dp[i−1][j],容量不足,不放入物品imax(dp[i−1][j],dp[i−1][j−v[i]]+w[i]),考虑拿这件物品能否获得更大重量
class Solution {
public:
int knapsack(int V, int n, vector<vector<int> >& vw) {
vector<vector<int>> dp(n + 1, vector<int>(V + 1, 0));
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= V; ++j) {
if (j < vw[i - 1][0]) {
dp[i][j] = dp[i - 1][j];
} else {
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - vw[i - 1][0]] + vw[i - 1][1]);
}
}
}
return dp[n][V];
}
};
由于dp问题具有无后效性,可以采用滚动数组的方式节省内存,此时状态转移方程化为:
if j >= v[i]:
dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
Charm Bracelet:定义一维的dp数组,每次从后向前刷新dp数组:
#include <iostream>
#include <vector>
using namespace std;
int main() {
int N, M;
cin >> N >> M;
vector<int> W(N, 0);
vector<int> D(N, 0);
vector<int> dp(M + 1, 0);
for (int i = 0; i < N; ++i) {
cin >> W[i] >> D[i];
}
for (int i = 0; i < N; ++i) {
for (int j = M; j >= 1; --j) {
if (j >= W[i]) {
dp[j] = max(dp[j], dp[j - W[i]] + D[i]);
}
}
}
cout << dp[M] << endl;
return 0;
}
full knapsack problem
你有一个背包,最多能容纳的体积是V。现在有n种物品,每种物品有任意多个,第 i 种物品的体积为vi,价值为wi,求这个背包至多能装多大价值的物品?
完全背包的状态转移方程和01背包极其相似:
d
p
[
i
]
[
j
]
=
{
d
p
[
i
−
1
]
[
j
]
,
容量不足,不放入物品
i
m
a
x
(
d
p
[
i
−
1
]
[
j
]
,
d
p
[
i
]
[
j
−
v
[
i
]
]
+
w
[
i
]
)
,
考虑拿这件物品能否获得更大重量
dp[i][j]= \begin{cases} dp[i-1][j],\quad 容量不足,不放入物品i\\ max(dp[i - 1][j], dp[i][ j - v[i]] + w[i]), \quad 考虑拿这件物品能否获得更大重量 \end{cases}
dp[i][j]={dp[i−1][j],容量不足,不放入物品imax(dp[i−1][j],dp[i][j−v[i]]+w[i]),考虑拿这件物品能否获得更大重量
而且,考虑使用滚动数组压缩空间,完全背包和01背包的状态转移方程是一样的。区别在于dp数组的遍历方式:完全背包问题必须顺序推导dp数组,而01背包采用逆序推导dp数组
if j >= v[i]:
dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
class Solution {
public:
vector<int> knapsack(int v, int n, vector<vector<int> >& nums) {
// write code here
vector<int> res;
vector<int> dp(v + 1, 0);
vector<int> pack(v + 1, -255);
pack[0] = 0;
for (int i = 0; i < n; ++i) {
for (int j = 0; j <= v; ++j) {
if (j >= nums[i][0]){
dp[j] = max(dp[j], dp[j - nums[i][0]] + nums[i][1]);
pack[j] = max(pack[j], pack[j - nums[i][0]] + nums[i][1]); //只考虑能从0一步步跳到v的w
}
}
}
res.push_back(dp[v]);
if (pack[v] > 0) {
res.push_back(pack[v]);
} else {
res.push_back(0);
}
return res;
}
};
fractional knapsack problem
与上面的两种背包问题不同,分数背包问题(物品可以被任意分割)比较简单,可以用贪心算法解决:每次都选择单位价值最大的物品装入背包,如果未满,则选择下一个价值次大的物品装入背包
#include <iostream>
#include <stdio.h>
#include <algorithm>
using namespace std;
#define MAXN (100+10)
struct Box {
int v;
int w;
double density;
Box() {}
Box(int vv, int ww) :v(vv), w(ww), density(double(v) / w) {}
bool operator<(const Box& b) {
return density < b.density;
}
};
int n, weigth;
double total_w = 0;
double total_v = 0;
Box boxes[MAXN];
int main() {
cin >> n >> weigth;
int v, w;
for (int i = 0; i < n; ++i) {
cin >> v >> w;
boxes[i] = Box(v, w);
}
sort(boxes, boxes + n);
for (int i = n - 1; i >= 0; --i) {
if (total_w + boxes[i].w < weigth) {
total_w += boxes[i].w;
total_v += boxes[i].v;
}
else {
total_v += boxes[i].density * (weigth - total_w);
total_w = weigth;
}
}
printf("%.1f\n", total_v);
return 0;
}