先放题目(应该是洛谷里的样例题目吧,侵删~ )
洛谷-P1048 采药
这个范例emmmm 我们重新编个范例输入,为了方便思考,我们先直接考虑花费时间和价值相等时的情况,也就是拿了多少价值就花掉多少时间
22 5
1 1
5 5
6 6
9 9
20 20
尝试拆解成子问题
比如对于第5个数20,我们只可能进行两个操作:拿,或者不拿
如果选择拿的话,问题就变成这样:
在时间2内,拿走下面的哪几个数,才使拿走的数的总和尽量更大?
编号 | 1 | 2 | 3 | 4 |
---|---|---|---|---|
数 | 1 | 5 | 6 | 9 |
这个子问题解决后(等下会将怎么解决这个子问题的),得出来的总和加上这一波的20,就是选择拿的答案。
这里子问题相比原问题有两个改变:
- 因为我们拿了20,也就花了20时间,时间就从原本的22变成了2
- 因为我们的第5个数已经拿走了,所以现在只用考虑前4个数了
如果选择不拿的话,问题就变成这样:
在时间22内,拿走下面的哪几个数,才使拿走的数的总和尽量更大?
编号 | 1 | 2 | 3 | 4 |
---|---|---|---|---|
数 | 1 | 5 | 6 | 9 |
这个子问题解决后(这个子问题跟上面的差不多,就时间不一样,等下也会来解决掉的),得出来的答案就是选择不拿的答案。(都不拿了肯定当然就不加20了,相应的,这个子问题的时间也没有减20)
于是可以发现,无论这里的20选择拿还是不拿,问题都能变成一个关于前4个数的子问题,只是限制的时间不太一样
最后通过解决这个子问题,得到最终选择拿的答案(拿的子问题总和加上这波拿的20)和不拿的答案(不拿的子问题总和),比较这两个答案谁更大,就能得到这一波该选择拿还是不拿
我们希望通过关于前4个数的子问题来决定第5个数是拿还是不拿,但子问题中,限制的时间是由第5个数字拿不拿决定的。。。。于是喜闻乐见地套娃了
所以我们把关于前4个数的子问题变成这样:
在时间 t (t ∈ [1, n], n = 22) 内,拿走下面的哪几个数,才使拿走的数的总和尽量更大?
编号 | 1 | 2 | 3 | 4 |
---|---|---|---|---|
数 | 1 | 5 | 6 | 9 |
这里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", ×[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;
}
抛砖引玉,大牛绕道~
如有错误,烦请指出~
参考文章