有 n n n 个重量和价值分别为 w i w_i wi, v i v_i vi 的物品。从这些物品中挑选出总重量不超过 W W W 的物品,求所有挑选方案中价值总和的最大值。
数据范围:
1 ≤ n ≤ 100 1\le n\le100 1≤n≤100
1 ≤ w i , v i ≤ 100 1\le w_i,v_i\le100 1≤wi,vi≤100
1 ≤ W ≤ 10000 1\le W\le10000 1≤W≤10000
d p [ 0 ] [ j ] = 0 d p [ i + 1 ] [ j ] = { d p [ i ] [ j ] , j < w i m a x { d p [ i ] [ j ] , d p [ i ] [ j − w i ] + v i } , j ≥ w i \begin{split} dp[0][j]&=0 \\ dp[i + 1][j]&=\begin{cases} dp[i][j]&,j
// 输入
int n, W; // n -- 物品个数;W -- 最大重量
int w[WMAX], v[VMAX]; // w[WMAX] -- 物品重量;v[VMAX] -- 物品价值
int dp[MAX][MAX] // dp数组,与记忆化数组一样,必须足够大
void solve(void)
{
for (int i = 0; i < n; i++)
{
for (int j = 0; j <= W; j++)
{
if (j < w[i])
dp[i + 1][j] = dp[i][j];
else
dp[i + 1][j] = max(dp[i][j], dp[i][j - w[i]] + v[i]);
}
}
printf("%d\n", dp[n][W]);
}
由 d p [ i + 1 ] [ j ] = m a x { d p [ i ] [ j ] , d p [ i ] [ j − w i ] + v i } dp[i + 1][j]=max\{dp[i][j],dp[i][j-w_i]+v_i\} dp[i+1][j]=max{dp[i][j],dp[i][j−wi]+vi},我们可以发现,虽然 d p [ M A X ] [ M A X ] dp[MAX][MAX] dp[MAX][MAX] 有 M A X MAX MAX 行,但是每次只访问 i i i 和 i + 1 i+1 i+1行。因此,我们只要用一个 d p [ 2 ] [ M A X ] dp[2][MAX] dp[2][MAX] 就能实现原来的功能。
// 输入
int n, W; // n -- 物品个数;W -- 最大重量
int w[WMAX], v[VMAX]; // w[WMAX] -- 物品重量;v[VMAX] -- 物品价值
int dp[2][MAX] // dp数组,与记忆化数组一样,必须足够大
void solve(void)
{
for (int i = 0; i < n; i++)
{
for (int j = 0; j <= W; j++)
{
if (j < w[i])
dp[(i + 1) & 1][j] = dp[i & 1][j];
else
dp[(i + 1) & 1][j] = max(dp[i & 1][j], dp[i & 1][j - w[i]] + v[i]);
}
}
printf("%d\n", dp[n & 1][W]);
}
&
按位与是一个双目操作符,对数字的二进制位进行操作。规则是:两个数字相对应的二进制位相同,运算结果为1;不同,则为0。所以,我们可以得到:
i & 1 = { 1 , i = 2 ∗ k + 1 0 , i = 2 ∗ k k = 0 , 1 , 2... i\&1=\begin{cases} 1&, i=2\ast k+1\\ 0&,i=2\ast k \end{cases} k=0,1,2... i&1={10,i=2∗k+1,i=2∗kk=0,1,2...
动态规划的核心是先记录,再访问。如果访问过后,数据就不要再需要了,那么覆盖这条记录也不会对结果造成影响。因此,我们只使用一个一维数组 d p [ M A X ] dp[MAX] dp[MAX] 也能达到目的。
我们再来看递推式:
d p [ 0 ] [ j ] = 0 d p [ i + 1 ] [ j ] = { d p [ i ] [ j ] , j < w i m a x { d p [ i ] [ j ] , d p [ i ] [ j − w i ] + v i } , j ≥ w i \begin{split} dp[0][j]&=0 \\ dp[i + 1][j]&=\begin{cases} dp[i][j]&,j
原来的代码是
for (int j = 0; j <= W; j++)
因为是从左向右的循环,所以肯定影响后来的计算。若改为从右往左计算,则能避免这样的问题。
d p [ j ] = m a x { d p [ j ] , d p [ j − w i ] + v i } , j ≥ w i dp[j]=max\{dp[j],dp[j-w_i]+v_i\},j\ge w_i dp[j]=max{dp[j],dp[j−wi]+vi},j≥wi
// 输入
int n, W; // n -- 物品个数;W -- 最大重量
int w[WMAX], v[VMAX]; // w[WMAX] -- 物品重量;v[VMAX] -- 物品价值
int dp[MAX] // dp数组,与记忆化数组一样,必须足够大
void solve(void)
{
for (int i = 0; i < n; i++)
{
for (int j = W; j >= w[i]; j--)
{
dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
}
}
printf("%d\n", dp[W]);
}
有 n n n 个重量和价值分别为 w i w_i wi, v i v_i vi 的物品。从这些物品中挑选出总重量不超过 W W W 的物品,求所有挑选方案中价值总和的最大值。在这里,每种物品可以挑选任意多件。
数据范围:
1 ≤ n ≤ 100 1\le n\le100 1≤n≤100
1 ≤ w i , v i ≤ 100 1\le w_i,v_i\le100 1≤wi,vi≤100
1 ≤ W ≤ 10000 1\le W\le10000 1≤W≤10000
d p [ 0 ] [ j ] = 0 d p [ i + 1 ] [ j ] = { d p [ i ] [ j ] , j < w i m a x { d p [ i ] [ j ] , d p [ i + 1 ] [ j − w i ] + v i } , j ≥ w i \begin{split} dp[0][j]&=0 \\ dp[i + 1][j]&=\begin{cases} dp[i][j]&,j
// 输入
int n, W; // n -- 物品个数;W -- 最大重量
int w[WMAX], v[VMAX]; // w[WMAX] -- 物品重量;v[VMAX] -- 物品价值
int dp[MAX][MAX] // dp数组,与记忆化数组一样,必须足够大
void solve(void)
{
for (int i = 0; i < n; i++)
{
for (int j = 0; j <= W; j++)
{
if (j < w[i])
dp[i + 1][j] = dp[i][j];
else
dp[i + 1][j] = max(dp[i][j], dp[i + 1][j - w[i]] + v[i]); // 与01背包唯一的不同
}
}
printf("%d\n", dp[n][W]);
}
// 输入
int n, W; // n -- 物品个数;W -- 最大重量
int w[WMAX], v[VMAX]; // w[WMAX] -- 物品重量;v[VMAX] -- 物品价值
int dp[2][MAX] // dp数组,与记忆化数组一样,必须足够大
void solve(void)
{
for (int i = 0; i < n; i++)
{
for (int j = 0; j <= W; j++)
{
if (j < w[i])
dp[(i + 1) & 1][j] = dp[i & 1][j];
else
dp[(i + 1) & 1][j] = max(dp[i & 1][j], dp[(i + 1) & 1][j - w[i]] + v[i]); // 与01背包唯一的不同
}
}
printf("%d\n", dp[n & 1][W]);
}
d p [ j ] = m a x { d p [ j ] , d p [ j − w i ] + v i } , j ≥ w i dp[j]=max\{dp[j],dp[j-w_i]+v_i\},j\ge w_i dp[j]=max{dp[j],dp[j−wi]+vi},j≥wi
// 输入
int n, W; // n -- 物品个数;W -- 最大重量
int w[WMAX], v[VMAX]; // w[WMAX] -- 物品重量;v[VMAX] -- 物品价值
int dp[MAX] // dp数组,与记忆化数组一样,必须足够大
void solve(void)
{
for (int i = 0; i < n; i++)
{
for (int j = w[i]; j <= W; j++)
{
dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
}
}
printf("%d\n", dp[W]);
}
与01背包比较,通过该方法优化后,二者代码的区别仅在第二个循环的方向:
for (int j = W; j >= w[i]; j--) \\ 从右往左
for (int j = w[i]; j <= W; j++) \\ 从左往右
完