动态规划基础
刚开始学DP,感觉很多DP转移方程很难理解...所以来补一补DP基础,从头捋一遍。
1.什么是DP?动态规划(dynamic programming)是运筹学的一个分支,是求解决策过程(decision process)最优化的数学方法。-----百度百科
这个定义看了让人更不懂了...我的理解就是,DP实际上是一种递推的记忆化搜索,它通过初始条件一步步递推,最终得到最优解。它不是一种特定的算法,而是一种思想,就像搜索一样。
来看看别人的定义:动态规划算法通常基于一个递推公式及一个或多个初始状态。 当前子问题的解将由上一次子问题的解推出。使用动态规划来解题只需要多项式时间复杂度, 因此它比回溯法、暴力法等要快许多。
2.DP基本原理:找到某个状态的最优解,再基于这个状态得到下一个状态的最优解,最终得到最优解。
3.DP基本应用:
(1)先来一道最基本的,POJ 1163 http://poj.org/problem?id=1163 数字三角形
题意:
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
在上面的数字三角形中寻找一条从顶部到底边的路径,使得路径上所经过的数字之和最大。路径上的每一步都只能往左下或 右下走。只需要求出这个最大和即可,不必给出具体路径。 三角形的行数大于1小于等于100,数字为 0 - 99
输入格式:
5 //表示三角形的行数 接下来输入三角形
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
要求输出最大和
解法:我们可以用一个二维数组D[][]储存三角形各个位置的数,D[i][j]表示第i行第j个数。再定义一个二维数组MaxSum[][]用于储存某个状态的最大和,MaxSum[i][j]表示点(i,j)到底边的最大和。
从D[i][j]出发,只能到达D[i+1][j]或者D[i+1][j+1],所以可以得出递推式:
if ( i == N) MaxSum(i,j) = D(i,j) else MaxSum(i, j) = max{ MaxSum(i+1,j), MaxSum(i+1,j+1) } + D(i,j)
可以得到如下递归代码:
int MaxSum(int i, int j){ if(i==n) return D[i][j]; int x = MaxSum(i+1,j); int y = MaxSum(i+1,j+1); return max(x,y)+D[i][j]; }
显然,对于每一行的某个数字,它都有两种选择可以到达下一个状态,所以这样做的时间复杂度是O(2^n)的,TLE。
所以要用记忆化的方式加速程序进行速度。
记忆化递归代码如下:
int maxSum[MAX][MAX]; int MaxSum(int i, int j){ if( maxSum[i][j] != -1 ) return maxSum[i][j]; if(i==n) maxSum[i][j] = D[i][j]; else{ int x = MaxSum(i+1,j); int y = MaxSum(i+1,j+1); maxSum[i][j] = max(x,y)+ D[i][j]; } return maxSum[i][j]; }
但我们不能满足于此,递归总是需要使用大量空间的,很可能造成栈溢出,所以我们还要想办法将递归转换成递推。
因为题目最终是要回到最后一行的,所以我们可以从最后一行出发,向上递推。
最后一行是4 5 2 6 5,对于其上面一行的2,它可以与4相加也可以与5相加,显然与5相加得到的状态更优。依次进行这种操作,可以得到倒数第二行7 12 10 10.
最后,我们可以得到新的三角形数表
30
23 21
20 13 10
7 12 10 10
4 5 2 6 5
仔细想一想,其实我们不必用二位数组存放最大和,用一个一维数组MaxSum[100]储存一行的最大和即可。更进一步的,我们可以直接用D的最后一行作为最大和数组。
#include <iostream> #include <algorithm> using namespace std; #define MAX 101 int D[MAX][MAX]; int n; int * maxSum; int main(){ int i,j; cin >> n; for(i=1;i<=n;i++) for(j=1;j<=i;j++) cin >> D[i][j]; maxSum = D[n]; //maxSum指向第n行 for( int i = n-1; i>= 1; --i ) for( int j = 1; j <= i; ++j ) maxSum[j] = max(maxSum[j],maxSum[j+1]) + D[i][j]; cout << maxSum[1] << endl; }
4.DP总结
一般思路:
(1).将原问题分解为形式相似、规模更小的子问题,得出子问题的局部最优解,进而得出最优解。
子问题的局部最优解得出后需要保存,这样每个子问题只需要处理一次即可。
(2).确定状态。什么是状态?我们往往把与子问题相关的一系列变量取值称之为状态,这个状态的值就是这个子问题的最优解。
整个问题的时间复杂度是状态数目乘以计算每个状态所需时间。在数字三角形里每个“状态”只需要经过一次,且在每个状态上作计算所花的时间都是和N无关的常数。
(3).确定边界状态的值。在数字三角形中,边界就是最后一行的数字。
(4).确定转移方程。得到某个状态后,我们需要一个转移方程来得到下一个状态。
5.
能用动规解决的问题的特点
1) 问题具有最优子结构性质。如果问题的最优解所包含的 子问题的解也是最优的,我们就称该问题具有最优子结 构性质。
2) 无后效性。当前的若干个状态值一旦确定,则此后过程的演变就只和这若干个状态的值有关,和之前是采取哪种手段或经过哪条路径演变到当前的这若干个状态,没有关系。