我知道这是一个老问题。但是我不得不花费一些时间搜索,现在我将记录这些方法以供将来参考。
方法1
使用N行的直接2D方法是:
int dp[MAXN][MAXW];
int solve()
{
memset(dp[0], 0, sizeof(dp[0]));
for(int i = 1; i <= N; i++) {
for(int j = 0; j <= W; j++) {
dp[i][j] = (w[i] > j) ? dp[i-1][j] : max(dp[i-1][j], dp[i-1][j-w[i]] + v[i]);
}
}
return dp[N][W];
}
这个方法使用O(NW)的空间。
方法2
您可能会注意到,在计算特定行的矩阵条目时,我们只查看前一行而不是之前的行。这可以利用,仅保留2行并交换它们的角色作为当前行和上一行。
int dp[2][MAXW];
int solve()
{
memset(dp[0], 0, sizeof(dp[0]));
for(int i = 1; i <= N; i++) {
int *cur = dp[i&1], *prev = dp[!(i&1)];
for(int j = 0; j <= W; j++) {
cur[j] = (w[i] > j) ? prev[j] : max(prev[j], prev[j-w[i]] + v[i]);
}
}
return dp[N&1][W];
}
这需要O(2W) = O(W)的空间。cur
是第i行,prev
是(i-1)行。
方法3
如果你再仔细看,会发现当我们在一行中写入一个条目时,我们只查看前一行中该条目左边的项目。我们可以利用这个方法使用单行并从右到左处理它,这样在计算一个条目的新值时,其左侧的条目具有旧值。这是一维表格方法。
int dp[MAXW];
int solve()
{
memset(dp, 0, sizeof(dp));
for(int i =1; i <= N; i++) {
for(int j = W; j >= 0; j--) {
dp[j] = (w[i] > j) ? dp[j]: max(dp[j], dp[j-w[i]] + v[i]);
}
}
return dp[W];
}
这个算法同样使用了O(W)的空间,但只用了一行。内循环必须反向,主要原因是当我们使用dp[j-w[i]]
时,需要上次外循环迭代的值,所以j
的值必须按照从大到小的顺序处理。
测试案例(来源于http://www.spoj.com/problems/PARTY/)
N = 10, W = 50
w[] = {0, 12, 15, 16, 16, 10, 21, 18, 12, 17, 18} // 1 based indexing
v[] = {0, 3, 8, 9, 6, 2, 9, 4, 4, 8, 9}
答案为 26
j
减少到w[i]
而不是0
,然后我们就可以得到for(int j = W; j >= w[i]; --j) dp[j] = max(dp[j], dp[j-w[i]] + v[i]);
。 - Peter Leedp[j]
(method1)应该对应于二维数组中的dp[i-1][j]
(method3),而不是dp[i][j]
,也就是说,我们需要从 i 循环的上一次迭代中获取dp[j]
的值,而不是当前迭代。 此外,请注意,由于所有的w[i]
都是正数,所以j-w[i] < j
,即我们只从正在写入的左侧槽位读取,从右侧槽位不读取。我们可以利用这一点将行数从 2 行减少到 1 行,同时仍然能够通过反转 j 循环来读取 i 循环的前一次迭代的值。 - avmohan