我们先来看下普通的朴素多重背包(拆成01背包求解)

n种物品,背包大小w,每种物品重量 wi,价值 vi,个数 ci

dp[j] 表示 大小为 j 的背包含有的最大价值,即 物品重量和 小于等于 j 时的 最大价值

#pragma GCC optimize("Ofast")
#include <bits/stdc++.h>
using namespace std;
struct node{int w,v,c;
}a[1005];
int n,dp[10005],w;
int main(){scanf("%d%d",&n,&w);for(int i=1;i<=n;i++) scanf("%d%d%d",&a[i].w,&a[i].v,&a[i].c);for(int i=1;i<=n;i++) for(int k=1;k<=a[i].c;k++) for(int j=w;j>=a[i].w;j--){dp[j]=max(dp[j],dp[j-a[i].w]+a[i].v);}printf("%d\n",dp[w]);return 0;
}

然后这个题吧,思路很简单,物品选择是有顺序的,按限制条件从小到大排序即可,下面给一标程

#include <cstdio>
#include <algorithm>
using namespace std;
struct node{int h,a,c;
}x[405];
int n,dp[40005],ans;
bool cmp(node a,node b){return a.a<b.a;
}
int main(){scanf("%d",&n);for(int i=1;i<=n;i++) scanf("%d%d%d",&x[i].h,&x[i].a,&x[i].c);sort(x+1,x+1+n,cmp);for(int i=1;i<=n;i++) for(int k=1;k<=x[i].c;k++) for(int j=x[i].a;j>=x[i].h;j--){dp[j]=max(dp[j],dp[j-x[i].h]+x[i].h);ans=max(ans,dp[j]);}printf("%d\n",ans);return 0;
}

思路很简单,你可能也会说,哎,我有很多不同的想法和写法

但是你可能会感到备受折磨的是,写完了,思路没啥毛病却WA,其实你的程序里的想法是有毛病的

那么我们下面就来分析几个代码看看怎么回事

WA代码一:(错误一:更新断层 + 错误二(在WA代码二中我们再一起说))

#include <cstdio>
#include <algorithm>
using namespace std;
struct node{int h,a,c;
}x[405];
int n,dp[40005],w;
bool cmp(node a,node b){return a.a<b.a;
}
int main(){scanf("%d",&n);for(int i=1;i<=n;i++) scanf("%d%d%d",&x[i].h,&x[i].a,&x[i].c);sort(x+1,x+1+n,cmp);for(int i=1;i<=n;i++) for(int k=1;k<=x[i].c;k++) for(int j=x[i].a;j>=x[i].h;j--){dp[j]=max(dp[j],dp[j-x[i].h]+x[i].h);w=max(w,x[i].a);}printf("%d\n",dp[w]);return 0;
}

这个错误你可以看下这样两句话,一个是背包模板里的,一个是这个错误代码一里的

(当然w这里其实也可以直接写作x[n].a,毕竟也排序了)

和模板的输出写法一样,唯一区别只在一小点,就是背包重量的循环这里

从最大重量w跑到a[i].w这个是模板写法,意味着每次更新都是[0 , w]区间(可以认为从0开始,[0,a[i].w)是保持原样也是被更新)

而这个错误代码是[0,x[i].a]

为什么这么写呢,因为你认为大于x[i].a不能再放了啊,没更新意义也不可能更新,因为不放啊

但你要注意,你输出的是dp[w],这是物品 <=w 时的最大价值,虽然背包中,但是物品可能很轻,你可以也需要给它更新的啊

你可能认为最后总是会把正确结果转移到的,你认为背包越大自然越大的价值就会转移过去,怎么还会小呢

而事实上,它是存在更新断层的,可能干这么说你不太理解,给你举个例子吧

比如第一轮,限制是20,你可能更到比方说,dp20 = 20,dp21乃至后面你没更,都是0

下一轮,比如重量价值是7,限制是40,那么问题来了,dp27 = 27,直到这里都还正确

dp28 = dp21 + 7 = 7,同样的 最大重量 dp40 也是 7,你输出 dp[w] 结果自然就是错的了,这就是更新断层

所以我们会想到如果dp21=20就正常了没问题了

所以对于任何背包问题,当你想完成的是,物品重量<=j下的最大value,那么你的更新范围就应当始终保持是[0,wmax] 

可能你现在有点懵逼,说为什么上面那个AC标程就是对的呢,好像写的差不多,我们慢慢来看。

然后这个代码实际上还有错误,我们结合下面的一起看

WA代码二:(错误二:条件最值与真实最值的混淆)

#include <cstdio>
#include <algorithm>
using namespace std;
struct node{int h,a,c;
}x[405];
int n,dp[40005];
bool cmp(node a,node b){return a.a<b.a;
}
int main(){scanf("%d",&n);for(int i=1;i<=n;i++) scanf("%d%d%d",&x[i].h,&x[i].a,&x[i].c);sort(x+1,x+1+n,cmp);for(int i=1;i<=n;i++) for(int k=1;k<=x[i].c;k++) for(int j=x[n].a;j>=x[i].h;j--){if(dp[j-x[i].h]+x[i].h<=x[i].a) dp[j]=max(dp[j],dp[j-x[i].h]+x[i].h);}printf("%d\n",dp[x[n].a]);return 0;
}

这个代码就是错误代码一把更新断层改掉后的代码,但是依然是错误的

我们可以看到,更新时我们有这样一句话,就是更新后不会超过限制,我们就给他更新

意思上是挺对的,但是问题就出在这里

这时候我们犯的错误叫做条件最值真实最值的混淆

可能这么说你不太懂,我们直接上例子,输入就是OJ样例的输入

然后第一轮更新用的是那个 5 23 8

更新得到 dp19=15   dp20=20 对吧

好,然后我们来看第二轮更新,用的是 7 40 3

dp40 = dp19 + 3*7 = 15 + 3*7 = 36 没问题

dp41呢,dp41 = dp20 + 2*7 = 34 因为 dp41 = dp20 + 3*7 = 41 > 40是超限制的

好这时出问题了,问题出在哪,我们来看

看这样几个值 对 dp41,34是条件最值,36是真实最值,41是理想最值

然后更新时,总是从上一个状态,比如这里是从 dp20 更新来

普通背包总是一直取理想最值,如果能取到理想最值就一直一点毛病都没有

但我们取不到,无条件下理想最值一定是最优的

但在限制条件下由上一状态转来的条件最值并不一定是最优的

你这么去理解,dp41 可以取到 34 36 41吧,dp20现在确认是理想最值,它转移过来的41是理想最值

他也能转移来34等值,36不是他转移来的,但是在限制条件下由dp20转移来的最大的是34

如果没有条件就没问题,条件下的真实最值并不一定是由上一状态转来的

能理解我所说的吗

而且这样一个东西问题很大,你用非真实最值继续向后做后续更新

就会出现很多不正确不准确的值

但是

正确的最大值永远会在某个位置出现,这个我们等下再讲

MLE+TLE但是结果正确的代码三

这个代码给大家看下,可以优化成不MLE但是依旧TLE的

为什么要给大家看,就是让大家感受下思路

这个代码也是有一个特点的,就是你任意重量背包,他的dp值都是正确的

好好理解下原因及原理

#include <cstdio>
#include <algorithm>
using namespace std;
struct node{int h,a;
}x[4005];
int n,dp[4005][40005],cnt=0;
bool cmp(node a,node b){return a.a<b.a;
}
int main(){scanf("%d",&n);for(int i=1;i<=n;i++){int h,a,c;scanf("%d%d%d",&h,&a,&c);while(c--) x[++cnt]=node{h,a};}sort(x+1,x+1+cnt,cmp);for(int i=1;i<=cnt;i++) for(int j=0;j<=x[cnt].a;j++){if(j<x[i].h) dp[i][j]=dp[i-1][j];if(j) dp[i][j]=max(dp[i-1][j],dp[i][j-1]);if(dp[i-1][j-x[i].h]+x[i].h<=x[i].a) dp[i][j]=max(dp[i][j],dp[i-1][j-x[i].h]+x[i].h);}   printf("%d\n",dp[cnt][x[cnt].a]);return 0;
}

看完这个代码,你可能还是有点小疑惑,但是我们不讲这个代码

我们直接讲下面更重要的内容,也是和这个代码的思想有点小关联的

这句话还记得吗,现在要讲这个了

这个不太好说也不太好证明,但是你可以理解一下

基本上只要是刚好卡在限制内的,它做出来的结果都是刚刚好的真实最值

基本上就是在每个重量临界点附近的值都是真实最值,重量临界点一定取的真实最值

重量临界点就是背包承重刚好等于放入物品总重,没有多余浪费掉的

这个不太好说明为什么,但是可以理解的,暂时不多说了。

下面这个是很多网上的题解给的代码

#include <cstdio>
#include <algorithm>
using namespace std;
struct node{int h,a,c;
}x[405];
int n,dp[40005],ans;
bool cmp(node a,node b){return a.a<b.a;
}
int main(){scanf("%d",&n);for(int i=1;i<=n;i++) scanf("%d%d%d",&x[i].h,&x[i].a,&x[i].c);sort(x+1,x+1+n,cmp);for(int i=1;i<=n;i++) for(int k=1;k<=x[i].c;k++) for(int j=x[n].a;j>=x[i].h;j--){if(dp[j-x[i].h]+x[i].h<=x[i].a) dp[j]=max(dp[j],dp[j-x[i].h]+x[i].h);ans=max(ans,dp[j]);}printf("%d\n",ans);return 0;
}

954ms基本快超时了,而且他也不是每一个dp值都能正确求得,但是最终答案最大值肯定没问题就是了

对比我一开始给你的AC代码

其实你只要一个最终正确的答案的话,也没必要每次都是 [0,w] 区间更新了

AC代码二

看了上面的诸多代码,你可能也想到了我为什么给出下面这样一个代码

MLE+TLE那个实现了每个dp值都是正确的,当然我们也可以基于AC代码一修改

然后利用之前所说的思想,最后加上这样一句话

然后就可以最完美的完成这样一个任务

#include <cstdio>
#include <algorithm>
using namespace std;
struct node{int h,a,c;
}x[405];
int n,dp[40005],ans;
bool cmp(node a,node b){return a.a<b.a;
}
int main(){scanf("%d",&n);for(int i=1;i<=n;i++) scanf("%d%d%d",&x[i].h,&x[i].a,&x[i].c);sort(x+1,x+1+n,cmp);for(int i=1;i<=n;i++) for(int k=1;k<=x[i].c;k++) for(int j=x[i].a;j>=x[i].h;j--){dp[j]=max(dp[j],dp[j-x[i].h]+x[i].h);}for(int i=1;i<=x[n].a;i++) dp[i]=max(dp[i],dp[i-1]);printf("%d\n",dp[x[n].a]);return 0;
}

总结

其实看了上面的一些代码吧,你可能也快总结出一些规律了

可能有些东西不是很好理解

但是规律你要记住要搞清楚

1、如果你想让每一个dp值都正确,即能够正确输出范围内任意重量背包下的最大价值

那么你需要做的是,每次更新区间必须是 [0,wmax] 以防更新断层,如果有限制条件,无法取到理想最值,一定要确认条件最值是否是真实最值,如果不是(一般情况下都会不是),那么你需要分析更改状态转移策略

或者你需要转用求解下面这个第二类问题的方法,最后做一个整体更新从而将各重量临界点附近的真实最值向后转移

2、如果你只想得到一个最终最大价值

那么你可以每次更新的范围 不固定,可以不是 [0,wmax] ,可以随着限制变化,但是这时候就有很多dp是非真实最值的,dp[wmax]可能不是真实最值,最后的ans可能会在某个重量临界点取到,那么你就需要一个ans,去不断记录更新这个最大值

这里再多说一句,还有一种错误叫做转移不全,当然这道题不存在这样一个问题,你要把转移不全更新断层区分好,一般来讲,出现转移不全这个问题的时候都是你把输出限制范围更新限制范围搞混了或者说没搞清,更新限制范围肯定是每一轮一点都不能缺的,比方说有个题他的背包是有负的重量的,他要求最终结果必须来自正重量的,这个就是输出限制,但是你更新的时候,你可以装成负的,再给装成正的,就是说过程中你不要装成负的就不装了,懂我意思吧,这个就是输出限制范围更新限制范围不要搞混。

POJ - 2392 朴素多重背包 + 贪心 WA与AC代码细节分析相关推荐

  1. Coins POJ - 1742(多重背包+是否装满问题)

    题意: 给定n种面值的硬币面值分别为WiW_{i}Wi​个数为CiC_{i}Ci​,问用这些硬币可以组成1~m之间的多少面值. 题目: People in Silverland use coins.T ...

  2. 【qduoj - 142】 多重背包(0-1背包的另类处理,dp)

    题干: ycb的ACM进阶之路 Description ycb是个天资聪颖的孩子,他的梦想是成为世界上最伟大的ACMer.为此,他想拜附近最有威望的dalao为师.dalao为了判断他的资质,给他出了 ...

  3. 多重背包模板 C++

    多重背包模板 多重背包: 有N种物品和一个容量为V的背包.第i种物品最多有numi件可用. 每件物品的重量是wi,价值是vi. 求解将哪些物品装入背包可使这些物品的重量总和不超过背包容量,且价值总和最 ...

  4. 背包问题(01背包,完全背包,多重背包(朴素算法二进制优化))

    写在前面:我是一只蒟蒻~~~ 今天我们要讲讲动态规划中~~最最最最最~~~~简单~~的背包问题 1. 首先,我们先介绍一下  01背包 大家先看一下这道01背包的问题   题目   有m件物品和一个容 ...

  5. POJ 1742 Coins ( 经典多重部分和问题 DP || 多重背包 )

    题意 : 有 n 种面额的硬币,给出各种面额硬币的数量和和面额数,求最多能搭配出几种不超过 m 的金额? 分析 : 这题可用多重背包来解,但这里不讨论这种做法. 如果之前有接触过背包DP的可以自然想到 ...

  6. (多重背包+记录路径)Charlie's Change (poj 1787)

    http://poj.org/problem?id=1787   描述 Charlie is a driver of Advanced Cargo Movement, Ltd. Charlie dri ...

  7. poj 3260 The Fewest Coins(多重背包+完全背包)

    http://poj.org/gotoproblem?pid=3260 (1)多重背包的处理方式:转化为分组背包(1,2,4,8,余数).具体细节参见代码: scanf("%d", ...

  8. 【POJ】 1014 Dividing(多重背包,优化)

    [POJ] 1014 Dividing(多重背包,优化) [题目链接]http://poj.org/problem?id=1014 题目 Description Marsha and Bill own ...

  9. 动态规划 背包问题小结 0-1背包(采药 九度第101题) 完全背包(Piggy-Bank POJ 1384) 多重背包(珍惜现在,感恩生活 九度第103题)

    本小结介绍0-1背包.完全背包以及多重背包问题 记忆要点: 0-1背包:二维数组情况下,顺序遍历体积或者倒序均可以                降维情况下需倒序遍历体积 完全背包:数组降维+顺序遍历 ...

最新文章

  1. 我国机器视觉企业体量偏小,上游零部件占利润大头
  2. SWFUpload上传
  3. Linux主机驱动与外设驱动分离思想
  4. Android中WebView加载sdcard中的html显示
  5. 程序员精进之路:性能调优利器--火焰图
  6. 举例说明事务隔离级别
  7. 防御CSRF、XSS和SQL注入***
  8. 深度学习二(Pytorch物体检测实战)
  9. 去除utf8文件的bom标记
  10. 图像局部特征(四)--FAST-ER角点检测子
  11. 解决PID 4、NT Kernal占用80、445等端口
  12. MATLAB代码:全面ADMM算法代码,实现了三种ADMM迭代方式 参考文档:《基于串行和并行ADMM算法的电_气能量流分布式协同优化_瞿小斌》
  13. 深入计算机组成原理(二十七)SIMD:如何加速矩阵乘法
  14. 我们的另一半,最熟悉的陌生人
  15. 10bit灰阶测试图_10bit色深是噱头?修图显示器有必要上10bit吗
  16. postgresql 12.0 源码编译安装
  17. Linux上安装pstree命令(-bash: pstree: command not found)
  18. 论文中 c.f. i.e. s.t. e.g. w.r.t. et al. etc英文缩写是什么意思
  19. 3D 沙盒游戏之地面网格设计
  20. 关于如何给机械革命深海泰坦添加ssd硬盘

热门文章

  1. Python(2.7)-元组(tuple)
  2. cocos2dx 回调函数
  3. Git学习笔记总结和注意事项
  4. ueditor 后端配置项没有正常加载,上传插件不能正常使用 UTF8 PHP
  5. SQL调优(SQL TUNING)之远程支持完成性能大幅优化
  6. php中浮点数计算问题
  7. setjmp 与 longjmp
  8. 使用DevExpress的WebChartControl控件绘制图表(柱状图、折线图、饼图)
  9. 关于premake4
  10. DbEntry on Mono 测试