动态规划基础

刚开始学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) 无后效性。当前的若干个状态值一旦确定,则此后过程的演变就只和这若干个状态的值有关,和之前是采取哪种手段或经过哪条路径演变到当前的这若干个状态,没有关系。

 

 

 

posted @ 2018-04-21 13:29  Zoez  阅读(149)  评论(0编辑  收藏  举报