一. 动态规划
动态规划(dynamic programming),与“分治思想”有些相似,都是利用将问题分 为子问题,并通过合并子问题的解来获得整个问题的解。于“分治”的不同之处在 于,对于一个相同的子问题动态规划算法不会计算第二次,其实现原理是将每一个计算过的子问题的值保存在一个表中。
二. 记忆化搜索
我们常见的动态规划问题,比如流水线调度问题,矩阵链乘问题等等都是“一步接着一步解决的”,即规模为 i 的问题需要基于规模 i-1 的问题进行最优解选择,通常的递归模式为DP(i)=optimal{DP(i-1)}。而记忆化搜索本质上也是DP思想,当子问题A和子问题B存在子子问题C时,如果子子问题C的最优解已经被求出,那么子问题A或者是B只需要“查表”获得C的解,而不需要再算一遍C。记忆化搜索的DP模式比普通模式要“随意一些”,通常为DP(i)=optimal(DP(j)), j < i。
三. 滑雪问题
上图显示为R*C的雪场,R是行数,C是列数。圆圈内的数字表示的是雪场的海拔高度h,根据常识我们知道,滑雪只有从上往下滑行才可能滑的动,现在我们想要求出能够滑行的最长距离,上面的例子我们可以很直接判断出25-24-......-1这个顺时针方向螺旋的滑雪方式可以滑的最远。
那么这个问题如何用编程来实现呢?我们发现这是一个典型的递推,DP(i, j)表示从坐标(i,j)出发所能滑行的最大长度,且有:DP(i, j)=optimal{DP(i±1, j±1)}+1。下面貼上源代碼。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 using namespace std; 6 const int max_size=110; 7 int R,C; 8 int dir[4][2]={{-1,0},{0,1},{1,0},{0,-1}}; 9 int h[max_size][max_size],dp[max_size][max_size]; 10 int inMap(int x,int y){ 11 if(x>=0&&x<=R-1&&y>=0&&y<=C-1) return 1; 12 return 0; 13 } 14 int max2(int a,int b,int c,int d){ 15 return max(max(a,b),max(c,d)); 16 } 17 int dfs(int i,int j){ 18 int nx,ny,down=0,up=0,left=0,right=0; 19 if(dp[i][j]) return dp[i][j]; 20 nx=i+dir[0][0]; ny=j+dir[0][1]; 21 if(inMap(nx,ny)){ 22 if(h[i][j]>h[nx][ny]) up=dfs(nx,ny); 23 } 24 nx=i+dir[1][0]; ny=j+dir[1][1]; 25 if(inMap(nx,ny)){ 26 if(h[i][j]>h[nx][ny]) right=dfs(nx,ny); 27 } 28 nx=i+dir[2][0]; ny=j+dir[2][1]; 29 if(inMap(nx,ny)){ 30 if(h[i][j]>h[nx][ny]) down=dfs(nx,ny); 31 } 32 nx=i+dir[3][0]; ny=j+dir[3][1]; 33 if(inMap(nx,ny)){ 34 if(h[i][j]>h[nx][ny]) left=dfs(nx,ny); 35 } 36 dp[i][j]=max2(up,down,left,right)+1; 37 return dp[i][j]; 38 } 39 int main(){ 40 scanf("%d%d",&R,&C); 41 memset(h,0,sizeof(h)); 42 memset(dp,0,sizeof(dp)); 43 for(int i=0;i<R;i++){ 44 for(int j=0;j<C;j++){ 45 scanf("%d",&h[i][j]); 46 } 47 } 48 int ans=-1; 49 for(int i=0;i<R;i++){ 50 for(int j=0;j<C;j++){ 51 ans=max(ans,dfs(i,j)); 52 } 53 } 54 printf("%d\n",ans); 55 }