POJ 1088 滑雪 排序后 dp 或者 记忆化搜索 两种写法

题目描述:

Michael喜欢滑雪百这并不奇怪, 因为滑雪的确很刺激。可是为了获得速度,滑的区域必须向下倾斜,而且当你滑到坡底,你不得不再次走上坡或者等待升降机来载你。Michael想知道载一个区域中最长底滑坡。区域由一个二维数组给出。数组的每个数字代表点的高度。下面是一个例子
 1  2  3  4 5

16 17 18 19 6

15 24 25 20 7

14 23 22 21 8

13 12 11 10 9

一个人可以从某个点滑向上下左右相邻四个点之一,当且仅当高度减小。在上面的例子中,一条可滑行的滑坡为24-17-16-1。当然25-24-23-...-3-2-1更长。事实上,这是最长的一条。
Input
输入的第一行表示区域的行数R和列数C(1 <= R,C <= 100)。下面是R行,每行有C个整数,代表高度h,0<=h<=10000。
Output
输出最长区域的长度。
Sample Input
5 5
1 2 3 4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9
Sample Output
25

找一个二维矩阵的最大下降子序列长度,这个没有固定起点和终点,所以直接遍历所有每一个点,深度优先搜索,不断更新最远的距离。不过矩阵100 * 100 , 要搜索10000个点,直接深搜肯定超时。

可以利用到记忆化递归,我个人觉得是用一个数组保存递归函数的一些必要参数,当再次递归到这里的时候,可以直接返回结果或直接返回不做处理,可以减少很多的重复递归。和动态规划很像。

不过,为什么这道题目可以用记忆化递归呢?我觉得是搜索完一个点之后,不会出现这个点的其它解。在这里,搜索当前点,就会搜索到当前点的所有满足下降条件的相邻点,也就是深搜的下一层的所有点,就像BFS的层次一样。所以当再次访问到这里的时候,任何带有当前点的解都在之前搜索过了。

例如,当前搜索点是 xu , 搜索 xu 完毕之后得到的最远一段是  xu ..... x3....x2x0 , 下一次再经过 xu 的时候,要搜索 xu , 其实从 xu 出发的最优解已经被确定了,就是xu ..... x3....x2x0,假设这次搜索找到了其他更优解,那第一次从 xu 搜索的时候就一定会找到这个解。所以,发现这个点曾经搜索过,就直接返回从这个点出发得到的最优解 , 这就是一个简单的记忆化递归 例子。

不用暴力搜索整个矩阵,如果一个点 u 的四个邻接点中有更高的点,从 u 出发一定得不到全局最优解,而且以后一定还会再回到 u 进行搜索,递归深度增加, 所以可以放弃从 u 出发搜索,改为选择那些比周围四个点都更高的点,作为深搜的起点,从这些点出发得到的全局最优解是最有可能的。

#include <iostream>  
#include <cstring>  
#include <cstdio>  
using namespace std ;  
int dp[105][105] ; // dp[i][j] 代表 ( i , j )这个位置可以下滑的最远距离,存储当前点搜索的最优解  
int a[105][105] ;    
int dx[4] = { 0 , 0 , -1 , 1 } ;  
int dy[4] = { 1 , -1 , 0 , 0 } ;  
int ans , curx , cury ;  
int is( int i , int j ) {
    return !( a[i-1][j] > a[i][j] || a[i][j-1] > a[i][j] || a[i+1][j] > a[i][j] || a[i][j+1] > a[i][j] ) ;
}
  
int DFS( int x , int y ){  
    if( dp[x][y] > 1 )                      // 搜索过了  
        return dp[x][y] ;  
    for( int i = 0 ; i < 4 ; ++i ){         // 从四个方向中选一个  
        curx = x + dx[i] ;                  // 如果满足下降  
        cury = y + dy[i] ;  
        if( a[curx][cury] != -1 && a[curx][cury] < a[x][y] )  
            dp[x][y] = max( dp[x][y] , DFS( curx , cury ) + 1 ) ;  
    }                                       // 从这个方向继续搜素, 更新当前点可以下滑的最大距离  
    return dp[x][y] ;  
}  
  
int main(){  
    int n , m , i , j ;  
    scanf( "%d%d" , &n , &m ) ;  
    memset( a , -1 , sizeof( a ) ) ;  
    for( i = 1 ; i <= n ; ++i )  
        for( j = 1 ; j <= m ; ++j )  
            scanf( "%d" , &a[i][j] ) , dp[i][j] = 1 ;  
    ans = 0 ;  
    for( i = 1 ; i <= n ; ++i )  
        for( j = 1 ; j <= m ; ++j ) 
            if( is( i , j ) )       // 这个点比周围四个点大
                ans = max( ans , DFS( i , j ) ) ;  
    cout << ans << endl ;                  
    return 0 ;  
}  

这道题目,也可以用 dp 来做,方程和上面的记忆化递归一样,都是比较和周围四个可以滑行的距离。

不过需要先排序,因为 dp 具有无后效性,我选择从高滑行到低。从当前开始滑行时,要先保证当前所有更高的地方都开始滑行了,因为当前点是可以从这些更高的点滑下来的。如果当前点滑完了,发现还有一个更高的点可以使得滑行距离更远,那就可能更新不到最远。举个例子,当前搜索的点是 30,周围四个点是 20 10 5 100, 滑雪会更新从 30 出发可以滑行的距离,但是30无法滑到 100 , 之后再访问 100 的时候,并不会加上 100 到 30 这一段,因为从 30 出发的距离比 100 到 30 长。

保存每一个点的高度和 x, y 坐标,写个比较,按照高度从大到小排序。每到一个点,更新周围几个点已经滑行的距离。

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std ;
int a[105][105];  
int dp[105][105];  
int dx[4] = { 0 , 0 , -1 , 1 } ;
int dy[4] = { 1 , -1 , 0 , 0 } ;
int ans ;
struct Node{ 
    int data , x , y ;
} One[105*105] ;
bool cmp( const Node &a , const Node &b ) { return a.data > b.data ; }

int main()  
{  
    int n , m , i , j , k , curx , cury , x , y , top = 0 ;  
    cin >> n >> m ;
    memset( a , -1 , sizeof( a ) ) ;
    for( i = 1 ; i <= n ; i++ )  
        for( j = 1 ; j <= m ; j++ ){
            scanf( "%d" , &a[i][j] ) ;
            One[++top].data = a[i][j] , One[top].x = i , One[top].y = j ;
        }
    sort( One+1 , One+top+1 , cmp ) ;
    for( k = 1 ; k <= top ; ++k ){
        x = One[k].x , y = One[k].y ;
        for( i = 0 ; i < 4 ; ++i ){
            curx = x + dx[i] , cury = y + dy[i] ;
            if( a[curx][cury] != -1 && a[curx][cury] < a[x][y] )
                dp[curx][cury] = max( dp[curx][cury] , dp[x][y] + 1 ) ;
        }
    }
    for( i = 1 ; i <= n ; ++i )
        for( j = 1 ; j <= m ; ++j )
            ans = max( ans , dp[i][j] ) ;
    cout << ans+1 << endl ;    // 每次都是加上旁边,没加上自己
    return 0;  
}  

记忆化递归 0ms , 但是 dp 32 ms .....

如有错误,敬请指正



  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
资源包主要包含以下内容: ASP项目源码:每个资源包中都包含完整的ASP项目源码,这些源码采用了经典的ASP技术开发,结构清晰、注释详细,帮助用户轻松理解整个项目的逻辑和实现方式。通过这些源码,用户可以学习到ASP的基本语法、服务器端脚本编写方法、数据库操作、用户权限管理等关键技术。 数据库设计文件:为了方便用户更好地理解系统的后台逻辑,每个项目中都附带了完整的数据库设计文件。这些文件通常包括数据库结构图、数据表设计文档,以及示例数据SQL脚本。用户可以通过这些文件快速搭建项目所需的数据库环境,并了解各个数据表之间的关系和作用。 详细的开发文档:每个资源包都附有详细的开发文档,文档内容包括项目背景介绍、功能模块说明、系统流程图、用户界面设计以及关键代码解析等。这些文档为用户提供了深入的学习材料,使得即便是从零开始的开发者也能逐步掌握项目开发的全过程。 项目演示与使用指南:为帮助用户更好地理解和使用这些ASP项目,每个资源包中都包含项目的演示文件和使用指南。演示文件通常以视频或图文形式展示项目的主要功能和操作流程,使用指南则详细说明了如何配置开发环境、部署项目以及常见问题的解决方法。 毕业设计参考:对于正在准备毕业设计的学生来说,这些资源包是绝佳的参考材料。每个项目不仅功能完善、结构清晰,还符合常见的毕业设计要求和标准。通过这些项目,学生可以学习到如何从零开始构建一个完整的Web系统,并积累丰富的项目经验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值