可以先看下这篇博客理解下动态规划的思想:初识动态规划
写在前面的:这篇博客主要写的是,一个容量为v的背包去装n个物品能获得的最优解的问题
问题简述:现在有一个背包,它能容纳的最大重量为v,问背包所能带走的最大价值是多少?
01背包:有n个物品,每个物品的重量为w[i],每个物品的价值为h[i]。
[对于每个物品不可以取多次,最多只能取一次,之所以叫做01背包,0表示不取,1表示取]
多重背包:有n种物品,每个物品的重量为w[i],每个物品的价值为h[i],每种物品有c[i]个。
完全背包:有n种物品,每个物品的重量为w[i],每个物品的价值为h[i],每种物品有无限个。
1. 最基础的二维数组解01背包问题
设dp[i][j]表示处理到第i件物品时,容量为j的背包能获得的最大价值,处理结束后dp[n][v]就是我们所要求解的值。背包问题第一层循环都是跑的物品总类数(目前我遇到的都是这样子),一类一类地处理n类物品。当我们处理到第i件物品时,我们知道前i-1件物品在背包容量为1、2、3、、、n时能获得的最大价值分别是dp[i-1][1]、dp[i-1][2]、dp[i-1][3]、、、dp[i-1][v]。既然是01背包,我们对第i件物品就有两种策略:不取——那就好办了,容量为j的背包所能装的价值在i-1的基础上不变,就是dp[i-1][j];取——容量为j的背包就得腾出w[i]的空位放物品i,此时的价值就为dp[i-1][j-w[i]]+h[i],所以dp[i][j]的最优解就是在这两种情况之间,dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+h[i])。就这样,跑完第二层循环我们就可以得到dp[i][j]的值(j=1、2、3、、、v)。
—注意:我们对第i件物品的处理都是基于前i-1件物品的存放结果的,而前i-1件物品的存放结果第i件物品没有参与,这也是01背包问题一维优化的关键点。
—其实跑完了之后我们会发现,我们可以得到背包容量在1~v范围内的时去装这n件物品能获得的最大价值。
—后面的都是根据这个衍生出来的,而且后面完全背包和多重背包问题都能转化成01背包。
—背包中有时可以看到一个小优化,就是w[i]
w[j]&&h[i]
h[j]是可以直接把j物品去掉,但不常用,(我没用过/哭笑),因为它不能优化最坏情况的复杂度。
代码:
memset(dp,0,sizeof(dp));
for(i=1; i<=n; i++)
for(j=w[i]; j<=v; j++)//二维数组这里从w[i]到v,或者从v到w[i]没有区别
dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+h[i]);
2. 01背包(一维)
处理完第i件物品时,dp[j]就表示用容量为j的背包去装前i件物品能获得的最大价值
基于上面说的二维的解法,既然处理第i件物品只与上一层(i-1)有关,那么我们就可以将二维数组转化为一维数组,转化的过程中需要考虑一个问题:怎样才能保证我们得到的结果的组成中,每件物品只参与了一次。
因为我们还是要处理n件物品,所以第一层循环不变(总不可能说物品处理的顺序会影响结果吧),然后看第二层循环,我们还是要通过第二层循环得到dp[i][j]的值(j=1、2、3、、、v),所以我们还是要遍历w[i]到v,只不过这里必须从v开始处理到w[i],因为我们处理dp[j]时会用到dp[j-w[i]]的值,而这个
dp[j-w[i]]必须是前i-1件物品参与的结果,不能有第i件物品的参与。如果我们从前往后处理,先处理dp[j-w[i]],在处理dp[j]的时候,构成dp[j-w[i]]最优解的可能也有第i件物品的参与,这样的话我们就不能保证每件物品值参与了一次。
- O(n*v),(常用)
memset(dp,0,sizeof(dp));
for(i=1; i<=n; i++)
for(j=v; j>=w[i]; j--)
dp[j]=max(dp[j],dp[j-w[i]]+h[i]);
3. 完全背包
dp[j]的意义和前面一样,只不过每件物品能取无穷多次
解法:
- 直接按完全背包的写法,O(n*v),(常用)
因为每件物品可以不止取一次,所以要得到最优解,处理dp[j]时用到的dp[j-w[i]]就必须是第i件物品参与的结果,所以第二层循环从前往后走就行了
memset(dp,0,sizeof(dp));
for(i=1; i<=n; i++)
for(j=w[i]; j<=v; j++)
dp[j]=max(dp[j],dp[j-w[i]]+h[i]);
- 转化为01背包,大于O(v *
(v/w[i])) > O(n * v),(后面两种解法可以先看多重背包,再回来看)
物品个数上线为k=v/w[i]
memset(dp,0,sizeof(dp));
for(i=1; i<=n; i++)
for(int k=1; k<=v/w[i]; k++)
for(j=v; j>=w[i]; j--)
dp[j]=max(dp[j],dp[j-w[i]]+h[i]);
- 转化为01背包+二进制优化,O(v * log(v/w[i]))>O(n*v),(常用)
memset(dp,0,sizeof(dp));
for(i=1; i<=n; i++)
for(int k=1; k*w[i]<=v; k<<=1)
for(j=v; j>=w[i]; j--)
dp[j]=max(dp[j],dp[j-k*w[i]]+k*h[i]);
4. 多重背包
dp[j]的意义还是和前面一样,每件物品能取有限次
解法:
- 转化为01背包,O(n * v * c)
把c[i]个物品i看成c[i]个不同的物品
memset(dp,0,sizeof(dp));
for(i=1; i<=n; i++)
for(int k=1; k<=c[i]; k++)
for(j=v; j>=w[i]; j--)
dp[j]=max(dp[j],dp[j-w[i]]+h[i]);
- 转化为01背包+二进制优化,O(v * logc[i])>O(n*v),(常用)
for(int i=1; i<=n; i++)
{
for(int k=1; k<=c[i]; k<<=1)
{
for(int j=v; j>=k*w[i]; j--)
dp[j]=max(dp[j],dp[j-k*w[i]]+k*h[i]);
c[i]-=k;
}
if(c[i])
for(int j=v; j>=c[i]*w[i]; j--)
dp[j]=max(dp[j],dp[j-c[i]*w[i]]+c[i]*h[i]);
}
- 转化为完全背包+数量的限制条件:多重背包的 二进制优化 / 转化为有限制的完全背包
加在后面的:对每一道动态规划题目都思考其方程的意义以及如何得来,加深对动态规划的理解