学习笔记:动态规划
先来看一个问题:
小张现在有8个任务可选,每个任务都必须在规定的时间段完成不能多也不能少,而且每个任务都有对应的报酬如下图,问小张应如何选择才能拿到最多的报酬?
首先试试贪心能不能解决,怎么贪心呢?优先选报酬夺的?还是优先选时间短的?我们不妨来试试。优先选报酬多的,那么他就会选任务3和任务8,能得到12元;优先选时间短的,那么他就有很多选法,是不是报酬最多的也要看运气,所以贪心并不能解决这个问题。
那我们换种方法来解决这个问题吧,
首先,每个任务都有选和不选两种选择,我们从最后一个任务开始模拟这个过程。首先我们需要先用一个数组
[ ],那么
表示在所有编号小于
的任务中,编号最接
近且时间段与
紧密相连(就是做完一个任务紧接着做下一个任务)的任务编号。那么这个数组就可以表示为下表:
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | |
---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 1 | 0 | 2 | 3 | 5 |
开始模拟选与不选的过程:首先我们用一个 [ ]数组, 表示第 个任务能获得的报酬的最优解(此最优解不表示第 个任务的报酬)
第8个任务有两种选择:选第8个任务,那么
,它表示如果选了第8个任务,也选了之前的某个任务,那么之前的某个任务就不能与第8个任务有时间冲突,所以这种情况下的第8个任务的报酬就是选了的之前某个任务的最优报酬+第8个任务的报酬;不选第8个任务,那么第8个任务的最优报酬就等于第7个任务最优报酬。为了便于理解,我画了一个图:
假设数组
表示第
个任务的最优报酬,那么上述过程就可以简化为下面这个方程:
此过程可递归实现,注意递归出口:
.
如果数据足够大的话,递归就不行了,我们再来看上面的那幅图:
注意到递归重复算了
,这无疑是对时间和空间的浪费,这种问题叫做重叠子问题,我们已经递归得算出来了
和
,那为何不存储下来,如果以后还要用到它们就可以直接取出来用就行了,这种方法就叫做记忆化存储。那我们就想办法优化一下,这次我们不从最后一个开始选了,我们就从第一个任务开始选,于是用一个循环就能解决问题(循环过程记忆化存储
),时间复杂度为
。状态转移方程如下:
最后再来说一说动态规划题目得特点即基本思想:对于一个给定的问题,这个问题可以分解为若干性质相同或相似的子问题,且每个子问题都有其最优解的解决方法,那么这个大问题的最优解就可以由若干个子问题的最优解结合得到。对于上述问题,我之所以要先从最后一个任务来推,就是为了发现,这个大问题的最优解可以由它的子问题的最优解来得到,并且还发现了重叠子问题,这时就需要记忆化存储,一旦某一个子问题的最优解已解出,就需要将其记忆化存储下来,以便下次再遇到同一个子问题就可以直接查表,从而减少时间和空间复杂度。所以动态规划常用于有重叠子问题和最优子结构的问题中。 当然动态规划不常用递归来做,一旦求出了状态转移方程就可以用迭代来做。
如何发现一个问题是否能用动态规划来做呢,首先要明白问题的最优解结构,以及子问题的最优解结构,如果它们的最优解结构相似或者相同的话,递归的模拟一下由大问题转换为子问题的过程,看是否有重叠子问题以及递归出口等。最后再总结出来状态转移方程就好了。
之后再补上例题。。。。。
来个简单的例题:
在讲述DP算法的时候,一个经典的例子就是数塔问题,它是这样描述的:
有如图所示的数塔,要求从顶层走到底层,若每一步只能走到相邻的结点,则经过的结点的数字之和最大是多少?
Input
输入数据首先包括一个整数C,表示测试实例的个数,每个测试实例的第一行是一个整数N(1 <= N <= 100),表示数塔的高度,接下来用N行数字表示数塔,其中第i行有个i个整数,且所有的整数均在区间[0,99]内。
Output
对于每个测试实例,输出可能得到的最大和,每个实例的输出占一行。
Sample Input
1
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
Sample Output
30
状态转移方程:
#include <stdio.h>
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
const int maxn=105;
int mp[maxn][maxn];
int main(){
int t;
scanf("%d",&t);
while(t--)
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++){
for(int j=1;j<=i;j++){
scanf("%d",&mp[i][j]);
}
}
for(int i=n-1;i>=0;i--){
for(int j=1;j<=i;j++)
mp[i][j]+=max(mp[i+1][j],mp[i+1][j+1]);
}
printf("%d\n",mp[1][1]);
}
return 0;
}