动态规划和记忆化搜索一些理解(1)

     这两天由于科银公司周年庆,所以多了两天的放假时间,于是乎抽了点时间看了看算法。以前对动态规划最优很浅很浅的理解,可以说除了弄懂了书上的那几个基本问题外,很少能够解决稍微难一点的动态规划题目。经过今天看书,上POJ,终于对动态规划有了感性认识。之后回想了一下以前做过的一些题目,恍然大悟,原来,特别是有关记忆化搜索的东西。

动态规划:就是一个最优化问题,先将问题分解为子问题,并且对于这些分解的自问题自身就是最优的才能在这个基础上得出我们要解决的问题的最优方案,要不然的话就能找到一个更优的解来替代这个解,得出新的最优自问题,这当然是和前提是矛盾的。动态规划不同于 贪心算法,因为贪心算法是从局部最优来解决问题,而动态规划是全局最优的。用动态规划的时候不可能在子问题还没有得到最优解的情况下就做出决策,而是必须等待子问题得到了最优解之后才对当下的情况做出决策,所以往往动态规划都可以用 一个或多个递归式来描述。而贪心算法却是先做出一个决策,然后在去解决子问题。这就是贪心和动态规划的不同。

一般遇到一个动态规划类型的问题,都先要确定最优子结构,还有重叠子问题,这两个是动态规划最大的特征,然后就是要写 动态规划的 状态方程,这个步骤十分十分的重要的,写动归方程是需要一定的经验的,这可以通过训练来达到目的。接着就是要自底向上的求解问题的,先将最小规模的子问题的最优解求出,一般都用一张表来记录下求得的解,到后来遇到同样的子问题的时候就可以直接查表得到答案,最后就是通过一步一步的迭代得出最后问题的答案了。

我的理解最重要的东西就是一定会要一个数组或者其他的存储结构存储得到的子问题的解。这样就可以省很多时间,也就是典型的空间换时间

动态规划的一种变形就是记忆化搜索,就是根据动归方程写出递归式,然后在函数的开头直接返回以前计算过的结果,当然这样做也需要一个存储结构记下前面计算过的结果,所以又成记忆化搜索

下面是一个典型的记忆化搜索的题目,来自PKU 1579

Function Run Fun

Time Limit:1000MS  Memory Limit:10000K
Total Submit:2402 Accepted:1373

Description
We all love recursion! Don't we?

Consider a three-parameter recursive function w(a, b, c):

if a <= 0 or b <= 0 or c <= 0, then w(a, b, c) returns:
1

if a > 20 or b > 20 or c > 20, then w(a, b, c) returns:
w(20, 20, 20)

if a < b and b < c, then w(a, b, c) returns:
w(a, b, c-1) + w(a, b-1, c-1) - w(a, b-1, c)

otherwise it returns:
w(a-1, b, c) + w(a-1, b-1, c) + w(a-1, b, c-1) - w(a-1, b-1, c-1)

This is an easy function to implement. The problem is, if implemented directly, for moderate values of a, b and c (for example, a = 15,

b = 15, c = 15), the program takes hours to run because of the massive recursion.


Input
The input for your program will be a series of integer triples, one per line, until the end-of-file flag of -1 -1 -1. Using the above

technique, you are to calculate w(a, b, c) efficiently and print the result.

Output
Print the value for w(a,b,c) for each triple.

Sample Input


1 1 1
2 2 2
10 4 6
50 50 50
-1 7 18
-1 -1 -1

Sample Output


w(1, 1, 1) = 2
w(2, 2, 2) = 4
w(10, 4, 6) = 523
w(50, 50, 50) = 1048576
w(-1, 7, 18) = 1

    这个题目如果直接用递归方程的话,肯定会超时(Time Limited Error),其实因为函数的递归会重复解决很多相当的子问题,一些子问题会被重复计算指数多次,所以为什么我们不要已经计算出的子问题的解记录下来呢,这就是记忆化搜索了,我们可以开一个数组用来记录已经解出的子问题,如果子问题的解在前面已经计算过,就直接返回,这就是本题要训练的要素。

这道题目的代码十分简单,就是在递归的函数前面添加几行就可以解决问题了,看看吧

cpp 代码
  1. #include 
  2. #define MAX 100000000   
  3.   
  4. int w(int a, int b, int c, int val[21][21][21])   
  5. {      
  6.     if (a <= 0 || b <=0 || c <= 0)   
  7.         return 1;   
  8.     else  
  9.         if (a > 20 || b > 20 || c > 20)   
  10.             return w(20, 20, 20, val);   
  11.     else  
  12.         if (val[a][b][c] != MAX)   
  13.             return val[a][b][c];   
  14.         else  
  15.         {   
  16.             if (a < b && b < c)   
  17.                 val[a][b][c] =  w(a , b, c-1, val) + w(a,b-1,c-1,val) - w(a,b-1,c,val);   
  18.             else  
  19.                 val[a][b][c] =  w(a-1,b,c,val) + w(a-1,b-1,c,val) + w(a-1,b,c-1,val) - w(a-1,b-1,c-1,val);   
  20.         }   
  21.         return val[a][b][c];   
  22. }   
  23.   
  24. int main()   
  25. {   
  26.     int val[21][21][21];   
  27.     int i, j , k;   
  28.     int a, b, c;   
  29.        
  30.     for (i = 0; i < 21; i++)   
  31.         for (j = 0; j < 21; j++)   
  32.             for (k = 0; k < 21; k++)   
  33.                 val[i][j][k] = MAX;   
  34.        
  35.     scanf("%d %d %d", &a, &b, &c);   
  36.   
  37.     while ( !(a == -1 && b == -1 && c == -1))   
  38.     {   
  39.         printf("w(%d, %d, %d) = %d\n", a, b, c, w(a, b, c, val));   
  40.         scanf("%d %d %d", &a, &b, &c);   
  41.     }   
  42.     return 0;   
  43. }  

先写到这里吧,明天再写(2)

你可能感兴趣的:(ACM-ICPC)