前几天第一次接触到01背包,也就是最基本的背包。当时也是看了很久的,然后大体都理解了,掺杂着状态方程与各种图画描述理解的。
基本的实现代码大概是这样子的
//n代表物体数,V代表背包的体积,weight为物体的重量
for(i=0;i<n;i++)
for(j=V;j>=0;j--)
{
if( j>=v[i] && dp[j-v[i]]+weight[i]>dp[i] )
dp[i]=dp[j-v[i]]+weight[i];
}
当时对于第二个循环 j=V从V开始,而不是从0开始,有点迷惑,后来理解了是为了避免重复计算:
比如说:当你更新第5个物品的时候(此时你各个体积的背包中的属性是当前物品当前体积下的最大重量),你必须且只能借用你更新了第四个物品后的背包去更新,假若j=V改成j=0,就会出现这样的情况 dp[5]已经是第五个物品下更新完后的状态了,当后面遇到dp[8]的更新要是用到dp[5]的话那么就会导致dp[8]重复计算了,所以必须要从后面更新到前面。
昨天有接触到了一个方案数背包的题目:hdu 2126
然后 “haha593572013”当时的有一部分的写法是
for(int i=1;i<=n;i++)
{
for(int j=0;j<v[i];j++)dp[i][j][0]=dp[i-1][j][0],dp[i][j][1]=dp[i-1][j][1];
for(int j=v[i];j<=m;j++)
{
if(dp[i-1][j-v[i]][0]+1>dp[i-1][j][0])
{
dp[i][j][0]=dp[i-1][j-v[i]][0]+1;
dp[i][j][1]=dp[i-1][j-v[i]][1];
}
else if(dp[i-1][j-v[i]][0]+1==dp[i-1][j][0])
{
dp[i][j][0]=dp[i-1][j][0];
dp[i][j][1]=dp[i-1][j][1]+dp[i-1][j-v[i]][1];
}
else
{
dp[i][j][0]=dp[i-1][j][0];
dp[i][j][1]=dp[i-1][j][1];
}
}
}
j是从0开始的,后来发现(还是以上面那个例子),它更新第五个物品的时候仍然是用到更新完第四个物品的背包,
虽然此时j是从0开始,但是由于它是二维数组,所以,更新完第四个物品的状态都保存在上一层的数组,不会改变,因此就不会出现重复计算。
如:
dp[i][j][0]=dp[i-1][j-v[i]][0]+1;
更新第i个物品时,是用到上一层更新完后的dp[i-1]下的状态,所以dp[i-1]是不会发生改变的。
最后写了一天的背包,多少有点理解:
首先是写出状态转移方程,找到思路。
然后给首层状态也就是“0”层状态(什么都没有操作下的状态)初始化,然后根据状态方程递推下去,求到第n层的解,总体有点像数学归纳。
根据状态方程写递推代码的时候,以hdu 2126为例.它的状态方程为
if(k>=p[i]) dp[i][j][k]+=dp[i-1][j-1][k-p[i]];
dp[i][j][k]+=dp[i-1][j][k];
只需要看dp的前两维,我们需要求dp[i][j],状态的转移都是来自于dp[i-1][j-1]或者是dp[i-1][j],这两者的状态都需要在dp[i][j]之前准备好,就可以递推了,如何准备好这两者的状态呢,明显,这么做就可以满足
<span style="font-size:24px;">for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++) </span>
这样就可以将dp[i-1]层的状态全部(全部,指的是i-1时对应的k=1,2......i)设置好,则dp[i]层便可以利用它进行转移
或者这么写
<span style="font-size:24px;"> for(int j=1;j<=n;j++)
for(int i=j;i<=n;i++) </span>
反正确认准备好了...就可以了,不用太纠结这点的..
最最后,状态递推的过程中,一定要保证状态的独立性,避免重复计算。
(此文是初学者接触背包几天后写下的复习笔记,若有错误或者可以改善的地方,或者有可以延伸到动态规划的知识深入系统的说明的地方,请各位大牛多多指点)