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

本文通过故事形式介绍了0-1背包问题,详细阐述了动态规划的解决思路,探讨了深谋远虑与回到过去的选择,并通过实例解释了动态规划的状态转移方程,揭示了解决此类问题的策略。
摘要由CSDN通过智能技术生成

0-1背包,即便是老生常谈也要讲讲故事
最近啊新开了博客嫌文章太少,先随便写点东西吧(太随便了吧!)

问题描述

我们有 n 种物品,其中物品 j 的重量为 wj ,价值为 vj
我们假定所有物品的重量和价格都是非负的。背包所能承受的最大重量为 W
求一种取物策略Strategy(i),意为取 Strategy(i) 个物品 i
要求在约束ni=1Strategy(i)wiW下,使得 ni=1Strategy(i)vi 最大。
如果限定每种物品只能选择 0 个或1个(即 Strategy(i){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个项。整个决策空间的大小将是 2N ;而对于每个决策,可以用 O(N) 的时间去求它的值;因此“深谋远虑”的代价是 O(2NN) ,简直爆炸,简直无解!

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

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

本挂逼表示要开挂了:
首先,我先检查一下自己身上的余钱

剩余 12 文钱

于是我决定同时开W个新的平行世界,分别叫做 α,β,...
哎算了太麻烦了,叫world 0,1,2,3,...,11 吧。
那么在world i 里面的那个“我”呢,身上有 i 文钱

笑话!我怎么可能让他们比我有钱呢?不然我不是很没面子?
(画外音:你确定你自己不是被更有钱的你自己创造出来的吗?)
(懵逼+陷入沉思……)

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

当我们看到第一件……两碗酒与一碟茴香豆套餐时,卧槽那个图片诱惑得不得了,我们看得眼都直了,一看标价: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[125]+98=98 或者 gain[12]=60 中的最大值 98。

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

world0123456789101112(我)
gain_old00000000060606060
gain000009898989898989898

机智的我们马上发现了如下一个事实:

gain(i)=max(gain(i5)+98,gain(i)),i>5

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

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

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

world0123456789101112(我)
gain_old_old00000000060606060
gain_old000009898989898989898
gain002229898100100100100100100

world 5: 我是不要这个2文的辣鸡的,因为我5文买98分的才能把5文钱用到极致。
world 7: 然而我恰好有闲钱买,哈哈哈,实力嘲讽5号位的兄弟。

现在我们就可以大致得出一个逻辑
每当我们碰到一个新的物品,先考虑其价格 cost , 然后考虑其价值 value
为啥先考虑价格?让我们来采访一下 Mr. Zero:

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

首先,你得买得起。
如果你买得起,你可以参考一下 worldicost 的兄弟,你除了比它多 cost 文钱以外,你可以问问他那些钱是怎么花的,以此来考虑配置你的花钱方式。
如果你买了这个玩意,那么你能获得的最大价值是 gain[icost]+v[i]
如果你没买这个玩意,那么你能获得的最大价值是 gain[i]
孰轻孰重自己考虑……显然地,每次都有这样的决策:
gain[i]max(gainold[i],gainold[icost]+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];
}

卧槽写了这么多辣鸡,浪费了好多时间啊……

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值