算法 动态规划(DP) 白话解释朴素01背包问题

先放题目(应该是洛谷里的样例题目吧,侵删~ )
洛谷-P1048 采药
采药
这个范例emmmm 我们重新编个范例输入,为了方便思考,我们先直接考虑花费时间和价值相等时的情况,也就是拿了多少价值就花掉多少时间

22 5
1 1
5 5
6 6
9 9
20 20

尝试拆解成子问题

比如对于第5个数20,我们只可能进行两个操作:,或者不拿


如果选择拿的话,问题就变成这样:
在时间2内,拿走下面的哪几个数,才使拿走的数的总和尽量更大?

编号1234
1569

这个子问题解决后(等下会将怎么解决这个子问题的),得出来的总和加上这一波的20,就是选择的答案。

这里子问题相比原问题有两个改变:

  1. 因为我们拿了20,也就花了20时间,时间就从原本的22变成了2
  2. 因为我们的第5个数已经拿走了,所以现在只用考虑前4个数了

如果选择不拿的话,问题就变成这样:
在时间22内,拿走下面的哪几个数,才使拿走的数的总和尽量更大?

编号1234
1569

这个子问题解决后(这个子问题跟上面的差不多,就时间不一样,等下也会来解决掉的),得出来的答案就是选择不拿的答案。(都不拿了肯定当然就不加20了,相应的,这个子问题的时间也没有减20)


于是可以发现,无论这里的20选择还是不拿,问题都能变成一个关于前4个数的子问题,只是限制的时间不太一样

最后通过解决这个子问题,得到最终选择的答案(拿的子问题总和加上这波拿的20)和不拿的答案(不拿的子问题总和),比较这两个答案谁更大,就能得到这一波该选择还是不拿

我们希望通过关于前4个数的子问题来决定第5个数是拿还是不拿,但子问题中,限制的时间是由第5个数字拿不拿决定的。。。。于是喜闻乐见地套娃了


所以我们把关于前4个数的子问题变成这样:
在时间 t (t ∈ [1, n], n = 22) 内,拿走下面的哪几个数,才使拿走的数的总和尽量更大?

编号1234
1569

这里t是变量,范围是1到22(22是范例里给的时间来着,无论咋样t最多肯定只有22了,也就是全部选不拿的情况),也就是把时间为1到22时都求一遍,这样第5个数无论是多少,都能取到或者不拿的结果(比如第5个数是范例中的20,那么对于拿的子问题,就有t=22-20=2,对于不拿的子问题,就有t=22,和上面推出来的一样,然后无论是2还是22都在t的范围内,也就都已经算好值等着拿了),也就是说,解出了这个前4个数的子问题的解,就必能解出前5个数的问题的解


同理的,对于前4个数的问题,也可以换成前3个数的子问题;前3个数的问题,也可以换成前2个数的子问题······最后,换成了前1个数的子问题,那就能直接算了(t 小于这个数时不够时间必取不了,总和就是0,t 不小于这个数时就只取这个数,因为只有一个数不取也没别的了,总和就等于这个数)

这时候我们求出了前1个数的问题的解,就能推前2个数的问题的解······最后,我们就能推出前5个问题的解,然后AC !


最后引用大佬解释的两个概念和判断一个问题能否使用DP解决

【无后效性】  
一旦f(n)确定,“我们如何凑出f(n)”就再也用不着了。  
要求出f(15),只需要知道f(14),f(10),f(4)的值,而f(14),f(10),f(4)是如何算出来的,对之后的问题没有影响。  
“未来与过去无关”,这就是无后效性。 
(严格定义:如果给定某一阶段的状态,则在这一阶段以后过程的发展不受这阶段以前各段状态的影响。)

【最优子结构】  
回顾我们对f(n)的定义:我们记“凑出n所需的最少钞票数量”为f(n).  
f(n)的定义就已经蕴含了“最优”。利用w=14,10,4的最优解,我们即可算出w=15的最优解。  
大问题的最优解可以由小问题的最优解推出,这个性质叫做“最优子结构性质”。

引入这两个概念之后,我们如何判断一个问题能否使用DP解决呢?  
能将大问题拆成几个小问题,且满足无后效性、最优子结构性质。

作者:阮行止
链接:https://www.zhihu.com/question/23995189/answer/613096905

最后附上递推代码

#include <cstdio>
#include <algorithm>

using namespace std;

int main(int argc, char const *argv[])
{
    int t, m, times[128], values[128], bags[1024] = { 0 };
    int i, j, k;
    scanf("%d%d", &t, &m);
    for (i = 0; i < m; i++)
    {
        scanf("%d%d", &times[i], &values[i]);
    }
    for (i = 0; i < m; i++)
    {
        // 这里循环时要从后往前,每一次都要用上一轮的结果
        // 如果从前往后用的时候就变成这一轮的结果了
        for (j = t; j >= times[i]; j--)
        {
            // 这一轮的结果等于选择不拿(bags[j])或者选择拿(values[i] + bags[j - times[i]])
            // 各得到的结果的最大值
            bags[j] = max(bags[j], values[i] + bags[j - times[i]]);
        }
    }
    printf("%d", bags[t]);
    return 0;
}

抛砖引玉,大牛绕道~
如有错误,烦请指出~

参考文章

  1. 知乎:什么是动态规划(Dynamic Programming)?动态规划的意义是什么?——阮行止的回答
  2. 知乎:什么是动态规划(Dynamic Programming)?动态规划的意义是什么?——王勐的回答
  3. 知乎:0-1背包问题的动态规划算法 ——Bat特白
  4. 入坑题 洛谷:P2392 kkksc03考前临时抱佛脚
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值