01背包问题

有n个重量和价值分别为w​i​,v​i​的物品。

从这些物品中挑选出总重量不超过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(2​n​)的时间,当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;
}