问题描述
有n种重量和价值分别为wi、vi(1≤i≤n)的物品,从这些物品中挑选总重量不超过W的物品,求出挑选物品价值总和最大的挑选方案,这里每种物品可以挑选任意多件。
问题求解
设置动态规划二维数组dp,dp[i][j]表示从前i个物品中选出重量不超过j的物品的最大总价值。
显然有边界条件:dp[i][0]=0(背包不能装入任何物品时,总价值为0),dp[0][j]=0(没有任何物品可装入时,总价值为0)→采用memset函数一次性初始化为0。
另外设置二维数组fk,其中fk[i][j]存放dp[i][j]得到最大值时物品i挑选的件数。
状态转移方程:
这样,dp[n][W]便是背包容量为W、考虑所有n个物品(同一物品允许多次选择)后得到的背包最大总价值,即问题的最优结果。
代码
int n, W;
int w[MAXN], v[MAXN];
int dp[MAXN + 1][MAXN + 1], fk[MAXN + 1][MAXN + 1];
int solve()
{
int i, j, k;
for (i = 1; i <= n; i++)
{
for (j = 0; j <= W; j++)
{
for (k = 0; k*w[i] <= j; k++)
{
if (dp[i][j] < dp[i - 1][j - k * w[i]] + k * v[i])
{
dp[i][j] = dp[i - 1][j - k * w[i]] + k * v[i];
fk[i][j] = k;
}
}
}
}
return dp[n][W];
}
void Traceback()
{
int i = n, j = W;
while (i >= 1)
{
cout << "物品" << i << "共" << fk[i][j] << "件";
j -= fk[i][j] * w[i];
i--;
}
}
算法分析
solve算法有三重循环,k的循环最坏可能从0到W,所以算法的时间复杂度为O(nW2)。
算法改进
实际上,上述算法中不必使用k循环,可以修改为在挑选物品i时直接多次重复挑选。
因为计算dp[i][j]中选择k(k≥1)个的情况与在dp[i][j-w[i]]的计算中选择k-1个的情况是相同的,所以dp[i][j]的递推中k≥1部分的计算已经在dp[i][j-w[i]]的计算中完成了。
int n, W;
int w[MAXN], v[MAXN];
int dp[MAXN + 1][MAXN + 1], fk[MAXN + 1][MAXN + 1];
int solve()
{
int i, j, k;
for (i = 1; i <= n; i++)
{
for (j = 0; j <= W; j++)
{
if (j < w[i])
dp[i][j] = dp[i - 1][j];
else
dp[i][j] = max(dp[i - 1][j], dp[i][j - w[i]] + v[i]);
}
}
return dp[n][W];
}
该算法的时间复杂度为O(nW)。