背包问题
0/1背包
题意概述
看完题意之后,模拟整个走的过程。
状态转移方程如下
cin >> n >> m ;
for (int i = 1 ; i <=n ; ++i)
{
cin >> v[i] >> w[i] ; // 体积,价值
}
for (int i = 1 ; i <= n ; ++i)
{
for (int j = 1 ; j <= m ; ++j)
{
if (j - v[i] >= 0)
{
f[i][j] = max(f[i-1][j] , f[i-1][j-v[i]] + w[i]) ;
}else
{
f[i][j] = f[i-1][j] ;
}
}
}
学过滚动数组,都可以知道此道题可以优化成一维数组,如果题目中不要求你算出整个过程,就可以采取这种办法,来达到优化空间的效果。
滚动数组概述
滚动数组其实就是一个递推的过程,我们学过的斐波那契数列和爬楼梯都可以采取这种办法来解决,例如 p = [1,2,3] ;现在让你算出这个数组的前缀和,那么就是[1,3,6],这就是一个简单的滚动数组,那么这道题目怎么化为一维的呢?有状态方程可以知道,f[i][j]的值只依赖于f[i-1]层,因此可以把f[i-1]全部去掉,但是注意一点f[i][j] = f[i-1][j] 其实是没有意义的,这样等价相当于没有改变下标为j的值,所有就不需要这一步,还值得注意的是,f[i][j] = max(f[i-1][j] , f[i-1][j-v[i]] + w[i]) 这个递推式是必须在j >= v[i]下才可以进行的,那么我们把第二层循环改成for (int j = m ; j >= v[i] ; j–),就满足j >= v[i],那么肯定会有人问为什么不改成for (int j = v[i] ; j <= m; ++j),这样不也满足 j >= v[i]吗,这里主要的原因是必须满足0/1背包只能拿一次,例如数组p = [1,2,3,4],我们从前往后进行累加p = [1,3,6,10],你会发现虽然每次加的都是前面一个数,但是其实把前面的前面的数也加上了,如果此时我第i个物品取了一次,dp[j] = max(dp[[j - v[i]] + w[i] , dp[j]) ,按照上述正序去遍历,如果背包为dp[3]在只有第一件物品的时候为6,dp[4] = dp[4-]+6 = 12 ,那么就是说我第一件物品去取了2次了,就和0/1问题相悖,那么如果我的p数组是从后往前的呢?p = [1,2,3,4] , 变化后p = [1,3,5,7],按照这种思维dp[4] = 6 , dp[3] = dp[3-1] + w[i] = 6,不会矛盾。
滚动数组代码
for (int i = 1 ; i <= n ; ++i) // 遍历物品
{
int v , w;
cin >> v >> w ;
for (int j = m ; j >= v ; j--) // 防止一个物品多次添加,并且满足j >= w[i]
{
f1[j] = max(f1[j] , f1[j - v] + w) ;
}
}
完全背包
这里就不放题目了,就是和0/1背包一样,但是每个物品可以重复放。
题意分析
在前i个物品,容量为m的时候,我们存在两个情况,如果当前容量放不下一个物品i的时候,f[i][j] = f[i - 1][j],如果可以放下那么f[i-1][j - k * v[i]] + k * w[i],有这种思想就可以上代码了。
for (int i = 1 ; i <= n ; ++i)
{
for (int j = 1 ; j <= m ; ++j)
{
for (int k = 0 ; k * v[i] <= j ; ++k)
{
f[i][j] = max(f[i][j] , f[i-1][j - k * v[i]] + k * w[i]) ;
}
}
}
代码优化
f[i][j] = max (f[i-1][j] , f[i-1][j - 1 * v[i]] + w[i] , f[i-1][j - 2 * v[i]] + 2 * w[i]);
f[i][j - v[i]] = max (f[i-1][j - v] , f[i-1][j - v[i] - 1 * v[i]] + w[i] , f[i-1][j - v[i] - 2 * v[i]] + 2 * w[i] …);
由这两个观察可以知道,f[i][j] = max(f[i-1][j] , f[i][j-v[i]] + w[i]),有了这个我们可以对代码进行再次优化。
for (int i = 1 ; i <= n ; ++i)
{
for (int j = 1 ; j <= m ; ++j)
{
if (j >= v[i])
{
f[i][j] = max (f[i][j], f[i][j-v[i]] + w[i]) ;
} else {
f[i][j] = f[i-1][j] ;
}
}
}
代码优化2
for (int j = v[i] ; j <= m; ++j)为从前往后进行遍历,主要是因为f1[j - v[i]] + w[i],根据上面的递推公式可以分析得知。
for (int i = 1 ; i <= n ; ++i)
{
for (int j = v[i] ; j <= m; ++j)
{
f1[j] = max(f1[j] , f1[j - v[i]] + w[i]) ;
}
}
总结
无