01背包问题
有n个重量和价值分别为wi,vi的物品。
从这些物品中挑选出总重量不超过W的物品,
求所有挑选方案中价值总和的最大值。
针对每个物品是否放入背包进行搜索
#include <iostream>
#define MAX_N 100
using namespace std;
int n, W;
int w[MAX_N], v[MAX_N];
int rec(int i, int j) { //从第i个物品开始挑选总重量小于j的部分
int res;
if (i == n) res = 0; //已经没有剩余物品了
else if (j < w[i]) res = rec(i + 1, j); //无法挑选这个物品
else res = max(rec(i + 1, j), rec(i + 1, j - w[i]) + v[i]); //挑选和不挑选的两种情况都尝试一下
return res;
}
int main() {
cin >> n;
for (int i = 0; i < n; i++)
cin >> w[i] >> v[i];
cin >> W;
cout << rec(0, W) << endl;
return 0;
这种方法的搜索深度是n,而且每一层的搜索都需要两次分支,最坏就需要O(2n)的时间,当n比较大的时候就没办法解了。
为了避免产生重复计算采用记忆化搜索
#include <iostream>
#include <cstring>
#define MAX_N 100
#define MAX_W 100
using namespace std;
int n, W;
int w[MAX_N], v[MAX_N];
int dp[MAX_N + 1][MAX_W + 1];
int rec(int i, int j) {
if (dp[i][j]) return dp[i][j]; //已经计算过的话直接使用之前的结果
int res;
if (i == n) res = 0;
else if (j < w[i]) res = rec(i + 1, j);
else res = max(rec(i + 1, j), rec(i + 1, j - w[i]) + v[i]);
return dp[i][j] = res;
}
int main() {
cin >> n;
for (int i = 0; i < n; i++)
cin >> w[i] >> v[i];
cin >> W;
memset(dp, 0, sizeof(dp));
cout << rec(0, W);
return 0;
}
对于同样的参数,只会在被调用到时执行递归部分,第二次之后都会直接返回。参数的组合不过nW种,而函数内只调用2次递归,所以只需要O(nW)的复杂度就能解决。
DP数组
记dp[i][j]根据rec的定义,从第i个物品开始挑选总重小于j时总价值的最大值。
递推公式:
dp[n][j]=0
if(j<w[i]) dp[i][j]=dp[i+1][j]
else dp[i][j]=max(rec(i+1,j),rec(i+1,j-w[i])+v[i])
#include <iostream>
#define MAX_N 100
#define MAX_W 100
using namespace std;
int n, W;
int w[MAX_N], v[MAX_N];
int dp[MAX_N + 1][MAX_W + 1];
int main() {
cin >> n;
for (int i = 0; i < n; i++)
cin >> w[i] >> v[i];
cin >> W;
for (int i = n - 1; i > -1; i--) {
for (int j = 0; j < W + 1; j++) {
if (j < w[i]) dp[i][j] = dp[i + 1][j];
else dp[i][j] = max(dp[i + 1][j], dp[i + 1][j - w[i]] + v[i]);
}
}
cout << dp[0][W];
return 0;
}