之前dp一直学得很混沌,写了几道题就晾在一边了,现在还是再来看看dp,从01背包看起。
一般dp的状态转移方程——考虑相邻项的数据关系:
对于固定的背包体积j
- 如果cost[i]<=j,可放,则考虑放与不放哪个更优,dp[i][j]=max(dp[i-1][j-cost[i]]+val[i],dp[i-1][j])
- 否则cost[i]>j,不可放,只有一种选择,不放,dp[i][j]=dp[i-1][j](易忘记更新)
hdu2602:
坑点:物品体积可能为0,即背包容量为0的话获得的最大价值可以非0,所以体积j要从0开始计算;但若没有物品的话,获得的最大价值肯定为0,所以物品i可以从1开始计算。
memset(dp,0,sizeof(dp)); for(int j=0;j<=v;j++) { for(int i=1;i<=n;i++) { if(cost[i]<=j) dp[i][j]=max(dp[i-1][j-cost[i]]+val[i],dp[i-1][j]); else dp[i][j]=dp[i-1][j]; } }
下面两份代码也是可行的
for(int i=1;i<=n;i++) { for(int j=0;j<=v;j++) { if(cost[i]<=j) dp[i][j]=max(dp[i-1][j-cost[i]]+val[i],dp[i-1][j]); else dp[i][j]=dp[i-1][j]; } }
代码*:for(int i=1;i<=n;i++) { for(int j=v;j>=0;j--) { if(cost[i]<=j) dp[i][j]=max(dp[i-1][j-cost[i]]+val[i],dp[i-1][j]); else dp[i][j]=dp[i-1][j]; } }
因为i是升序循环,在算dp[i][j]的时候,外层循环i-1的那一轮的每一项都已经 正确地(j降序计算也没问题)计算完了,即 从状态转移方程(if else两部分)等号右边向左边推导的时候,右边所需要的数据都已经先计算了出来。
但下面的程序是错误的,在算dp[i][j]的时候,
所以判断的关键就是上面加粗的那句话。
- if部分的状态转移方程,外层循环j-cost[i]那一轮已经算完了,但是那不是正确地计算完了,因为状态转移方程在进行推导的过程中,从等号右边向左边推导的时候,右边所需要的数据并没有先算出来。
- else部分的状态转移方程右边所需要的数据就更没有先算出来了。
for(int j=0;j<=v;j++) { for(int i=n;i>=1;i--) { if(cost[i]<=j) dp[i][j]=max(dp[i-1][j-cost[i]]+val[i],dp[i-1][j]); else dp[i][j]=dp[i-1][j]; } }
下面的是节省空间的写法,即上面标记为代码*的那份代码的简写:
for(int i=1;i<=n;i++) for(int j=v;j>=cost[i];j--) dp[j]=max(dp[j],dp[j-cost[i]]+val[i]);