您的位置:首页 > 其它

动态规划:趣话0-1背包

2016-03-09 15:21 295 查看
0-1背包,即便是老生常谈也要讲讲故事

最近啊新开了博客嫌文章太少,先随便写点东西吧(太随便了吧!)

问题描述

我们有 nn 种物品,其中物品 jj 的重量为 wjw_j,价值为 vjv_j。

我们假定所有物品的重量和价格都是非负的。背包所能承受的最大重量为 WW。

求一种取物策略Strategy(i)Strategy(i),意为取Strategy(i)Strategy(i)个物品ii

要求在约束∑ni=1Strategy(i)∗wi≤W\sum_{i=1}^{n}{Strategy(i)*w_i} \le W下,使得∑ni=1Strategy(i)∗vi\sum_{i=1}^{n}{Strategy(i)*v_i}最大。

如果限定每种物品只能选择00个或11个(即Strategy(i)∈{0,1}Strategy(i) \in \{0, 1\}),则问题称为0-1背包问题

问题的解

我们先来一个最朴素的解法。

// Language : C
// Mode : C99 and above
// solve : W(最大容量), n(物品数量), w(物品重量列表), v(物品价值列表)
// return : maxValueSum(最大价值和)
int solve(int W, int n, int* w, int* v){
// allocate the memory of the solution table and initialize
int** solutionTable = (int**) malloc(sizeof(int*) * n);
for(int i = 0; i < n; i++)
solutionTable[i] = (int*) malloc(sizeof(int) * (W + 1));
for(int i = 0; i < n; i++)
for(int j = 0; j <= W; j++)
solutionTable[i][j] = 0;

for(int i = 0; i < n; i++){ // for each item: i
for(int j = 0; j <= W; j++){ // for remaining weight: j
if(j > w[i]){ // there is still spare to get the item
if(i == 0)
solutionTable[i][j] = v[i];
else {
int stagegy1 = solutionTable[i - 1][j - w[i]] + v[i];
int stagegy2 = solutionTable[i - 1][j];
if(stagegy1 > stagegy2)
solutionTable[i][j] = stagegy1;
else
solutionTable[i][j] = stagegy2;
}
}
}
}

return solutionTable[n -1][W];
}


我可以负责任地告诉你们上面那段程序我不负责任没有编译测试过。

但我想应该是对的。

嗯,看得懂看不懂都不要紧,让我们开始接下来的话题。

深谋远虑 还是 回到过去

在这样一个决策问题里,一共有N个选择点,每个选择点可以选择2个项。整个决策空间的大小将是2N2^N;而对于每个决策,可以用O(N)O(N)的时间去求它的值;因此“深谋远虑”的代价是O(2NN)O( 2^NN),简直爆炸,简直无解!

要是我以后写Galgame,我一定要出这样一个这样原理的,20个选择点不过分吧?只有求得最优解才能获得完美结局(丧心病狂)。

没关系我手里还有外挂:平行世界监视器与世界线切换器。

本挂逼表示要开挂了:

首先,我先检查一下自己身上的余钱

剩余 12 文钱

于是我决定同时开W个新的平行世界,分别叫做α,β,...\alpha, \beta, ...

哎算了太麻烦了,叫world0,1,2,3,...,110, 1, 2, 3, ..., 11吧。

那么在world ii 里面的那个“我”呢,身上有 ii 文钱

笑话!我怎么可能让他们比我有钱呢?不然我不是很没面子?

(画外音:你确定你自己不是被更有钱的你自己创造出来的吗?)

(懵逼+陷入沉思……)

于是我们开始走进步行街开始买买买……

当我们看到第一件……两碗酒与一碟茴香豆套餐时,卧槽那个图片诱惑得不得了,我们看得眼都直了,一看标价:9文。

于是我与world 9, 10, 11 的 兄弟 便排出九文大钱,享用了一顿套餐,最后我们一致认为店家这个东西其实很扯,最多只能打60分。

可怜的world 0 ~ 8 的兄弟 表示 “我就这么默默看着你们这些高富帅装逼”。

world0123456789101112(我)
gain00000000060606060
我想,虽然觉得有点亏,但如果我们的人生就此结束了,我觉得我还是比 0~8 的兄弟们 多吃了 60 分的 东西,赚了。

但是,我们逛着逛着又看上了另外一件宝贝……滑稽面具:5文钱,我们兄弟13人围着面具评价了一番

卧槽这真像百度贴吧的滑稽表情,这逼我给98分,留两分怕它骄傲!

大伙表示我的评价很中肯。

然后……

我发现自己没钱买了,要是我一开始没吃坑爹的茴香豆套餐该多好啊!!

于是我掏出了外挂,回到过去……

具体的逻辑是:我本来有12文钱,如果这次买了我还有7文钱……那么

我相信world 7 的兄弟也是最聪明的(他也是挂逼),他在之前一定能把7文钱用到极致。

那么问题就很简单了:我看看用到极致的7文钱所获得的收益加上这98分滑稽面具能否超过我之前认为的极致策略。

综合两件物品的最大收益是: gain[12−5]+98=98gain[12 - 5] + 98 = 98 或者 gain[12]=60gain[12] = 60 中的最大值 98。

于是我们不要脸地回到过去退了茴香豆套餐,然后买了

world0123456789101112(我)
gain_old00000000060606060
gain000009898989898989898
机智的我们马上发现了如下一个事实:

gain(i)=max(gain(i−5)+98,gain(i)),i>5gain(i)=max(gain(i-5)+98, gain(i)), i > 5

这大概还只是一个猜想,让我们继续。

现在发现了 2文钱 价值 2 分的辣鸡。

同样的,从 world 2 的兄弟开始买得起了,然后world 3的兄弟会参考world 1 的兄弟的用钱方式来得出需不需要开挂(回到过去)……world 4 参考 world 2 ……

worldi world_i 参考 worldi−2world_{i-2}……(i>2 i > 2)

world0123456789101112(我)
gain_old_old00000000060606060
gain_old000009898989898989898
gain002229898100100100100100100
world 5: 我是不要这个2文的辣鸡的,因为我5文买98分的才能把5文钱用到极致。

world 7: 然而我恰好有闲钱买,哈哈哈,实力嘲讽5号位的兄弟。

现在我们就可以大致得出一个逻辑

每当我们碰到一个新的物品,先考虑其价格 costcost, 然后考虑其价值 valuevalue。

为啥先考虑价格?让我们来采访一下 Mr. Zero:

world 0:卧槽黑心仙商不要脸!叫我出来还不给我钱…

首先,你得买得起。

如果你买得起,你可以参考一下 worldi−costworld_{i-cost} 的兄弟,你除了比它多 costcost 文钱以外,你可以问问他那些钱是怎么花的,以此来考虑配置你的花钱方式。

如果你买了这个玩意,那么你能获得的最大价值是 gain[i−cost]+v[i]gain[i-cost] + v[i] 。

如果你没买这个玩意,那么你能获得的最大价值是 gain[i]gain[i]

孰轻孰重自己考虑……显然地,每次都有这样的决策:

gain[i]←max(gainold[i],gainold[i−cost]+v[i])gain[i] \leftarrow max(gain_{old}[i], gain_{old}[i-cost]+v[i])

注意这里有一个前提:你所依赖的兄弟是聪明的(即在上一次物品到达的时候考虑过调整的)

比如你不能简单地这样写

int solve(int W, int n, int* w, int* v){
if(n <= 0) return 0; // 没东西拿个卵
if(n == 1){
if(w[0] < W) return v[0]; // 好的拿得起
else return 0; // 哎哟拿不动
}
return max(solve(n - 1, W, w, v), solve(n - 1, W - w[n - 1]) + v[n - 1]); // wrong!!
}


而应该按照一开始的例子进行计算,一层层地更新。

当然,聪明或有经验的读者会发现可以在每层逆序地计算,这使得我们无须保存二维的solutionTable

// Language : C
// Mode : C99 and above
// solve : W(最大容量), n(物品数量), w(物品重量列表), v(物品价值列表)
// return : maxValueSum(最大价值和)
int solve(int W, int n, int* w, int* v){
// allocate the memory of the solution table and initialize
int* solutionTable = (int*) malloc(sizeof(int) * (W + 1));
for(int i = 0; i <= W; i++)
solutionTable[i] = 0;

for(int i = 0; i < n; i++){ // for each item: i
for(int j = W; j >= w[i]; j--){ // for remaining weight: j
if(i == 0)
solutionTable[j] = v[i];
else {
int stagegy1 = solutionTable[j - w[i]] + v[i];
int stagegy2 = solutionTable[j];
if(stagegy1 > stagegy2)
solutionTable[j] = stagegy1;
else
solutionTable[j] = stagegy2;
}
}
}

return solutionTable[W];
}


卧槽写了这么多辣鸡,浪费了好多时间啊……
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: