题目描述:
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更长。事实上,这是最长的一条。
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
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 .....
如有错误,敬请指正