前言
尽管计算机是门严谨的学科,但正因为严谨,所以要有趣味才能看得下去。在笔者的前几篇算法类文章中,都采用了以小故事作为引入的方式来介绍算法,但在回看的时候发现学术味还是太浓了,完全没有想看下去的欲望orz~因此笔者决定改组文章结构,将整个算法都以故事的形式呈现,至少让读者能看下去,并把算法类的文章统一成【算法宇宙——在故事中学算法】专栏,希望能帮助到大家!
正文
故事
小明是一个热爱探险,渴望在宇宙中遨游的少年,但他的父亲一直管束着他,不让他出去。终于,在他十八岁的生日上,他的父亲把他喊过来。
明父:儿子啊,你已经是个成熟的大人了,从今天开始,你可以去宇宙中探险了。
小明:好耶!!
明父:慢着,宇宙中危险遍地,海盗横行,所以在你出去之前,我有东西要给你。
(明父把小明带到他自己的房间,打开了一个大箱子,很多装备映入小明的眼帘)
小明:父亲,你把这些全都给我吗?谢谢父亲!
明父怒道:逆子,就算都给你了,你那艘小破飞船怎么装这么多装备?听着,现在这些装备的重量和杀伤力都写在上面,你可以随便挑,但如果你把你挑的装备都搬到飞船上之后,飞船飞不起来的话(因为装备太重了),你就别出去了,乖乖学算法,知道没有?
(明父出去了)
小明:这么多装备,我得思考一下……
(小明想了想,决定先按自己的直觉来。他拿起了第一件装备,向飞船上扔,然后拿起第二件继续扔,直到飞船即将超重为止。然后他满意的停下,之后他发现,外面还有很多重量小的高杀伤性武器,他无奈地笑了笑,把船上的武器都搬了下来重新选。)
小明:既然有这么多轻的武器,那么挑轻的来不就好了,量变引起质变嘛,我真聪明!
(他又开始向上搬武器,但这次他发现,好像有一些很重,但杀伤力极强的武器没有被选。他又花大力气把武器都搬了出来。)
小明:那这次我先搬杀伤力大的总行了吧!?
(很不幸的是,这次他只搬了几件武器,飞船就超重了,杀伤力甚至没有第一次强。)
抓耳挠腮的小明:哎呀,好烦啊。。都怪以前没有好好学算法,好后悔啊。。
(他思考良久也没能想出个所以然。无奈之下的他,垂着头去找了明父。)
得意的明父:就知道你小子不会搬,这次还不好好学习?行吧,这次我就告诉你怎么搬最好,但你以后去探险,也不能忘了学习算法啊!你记不记得,你以前做过一道采药的问题?(见记忆化搜索)
(明父拿出了一张表格)
明父:看好了臭小子,这张表格的两个维度分别是去哪一件武器和飞船为已经搬好的武器所分配的载重。觉不觉得和采药的那道题很相似?没错,这两道题其实是同一种题型,01背包问题。对01背包问题来说,转移方程都是固定的:
所以你知道怎么解决了吧?去把这个表格填好,然后赶紧去搬吧!
(小明拿过表格,沉思了一会。忽然,他灵光一闪。)
小明:父亲,你看这个转移方程,每一个i的更新只和i-1有关系,而且每次更新完i-1之后马上就更新i,并且我们实际上只需要最后一行的数据,那是不是意味着,我们可以只用一行存储数据呢?那么转移方程就可以是:
明父笑道:说得对!这是个很好的想法,但你要看清楚,这么做的话,由于你更新第j个数据时用到了上一次的第j个和第j-w[i]个,所以在更新数据的时候你要从后往前更新,才会保存更新用到的数据。
(小明欢呼了一声,拿着表格跑去搬装备了,搬完装备后,他驾驶着飞船,和父亲道别后,开始了属于自己的探险之旅。)
总结
小明的故事暂时告一段落了,但我们的算法还需要稍微解释一下。
f指的是选择第i件物品时,已被选择的物品如果占据了j这么多的载重,那么他们能创造的最大价值。
所以重新解释一下一开始的二维转移方程的第二项:如果选择取第i件物品,那么前面i-1件物品中选择的哪些物品就只能占据了j-w[i]这么多的载重,因为第i件物品分走了w[i]的载重。
其余的也没什么需要解释的了,直接上代码:
#include <iostream>
using namespace std;
const int maxn = 13010;
int n, W, w[maxn], v[maxn], f[maxn];
int main() {
cin >> n >> W;
for (int i = 1; i <= n; i++) cin >> w[i] >> v[i];
for (int i = 1; i <= n; i++)//循环n次,因为要对n件物品进行选择
for (int l = W; l >= w[i]; l--)//从后向前更新
f[l] = max(f[l], f[l - w[i]] + v[i]);//转移方程
cout << f[W];
return 0;
}
——————————————分割线————————————————
我是霜_哀,在算法之路上努力前行的一位萌新,感谢你的阅读!这次采用了以故事为主的结构,希望你能喜欢!码文不易,如果觉得好的话,可以关注一下,我会在将来带来更多更全面的算法讲解!