记忆化搜索概念
可以理解为DP+DFS,也就是:动态规划+搜索
目的
在搜索的过程中,会记录当前的状态,如果该状态之前已经搜索过了,直接返回就行了。
结果
减少了计算量和复杂度。
题目链接
题目描述
给定一个 R 行 C 列的矩阵,表示一个矩形网格滑雪场。
矩阵中第 i 行第 j 列的点表示滑雪场的第 i 行第 j 列区域的高度。
一个人从滑雪场中的某个区域内出发,每次可以向上下左右任意一个方向滑动一个单位距离。
当然,一个人能够滑动到某相邻区域的前提是该区域的高度低于自己目前所在区域的高度。
下面给出一个矩阵作为例子:
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−2−1。
在给定矩阵中,最长的滑行轨迹为 25−24−23−…−3−2−1,沿途共经过 25 个区域。
现在给定你一个二维矩阵表示滑雪场各区域的高度,请你找出在该滑雪场中能够完成的最长滑雪轨迹,并输出其长度(可经过最大区域数)。
输入格式
第一行包含两个整数 R 和 C。
接下来 R 行,每行包含 C 个整数,表示完整的二维矩阵。
输出格式
输出一个整数,表示可完成的最长滑雪长度。
数据范围
1≤R,C≤300,
0≤矩阵中整数≤10000
输入样例
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
题目分析
首先考虑暴力解决。
这个题一开始的思路就是用dfs进行搜索,一直搜索到不能往下搜为止,维护一个最大值。由于可以从任意点开始,所以我们就需要从二维矩阵中的任意一点开始搜索。
优化思路
我们发现,每一次重新从某个起始点开始搜索,基本都会搜到我们之前已经搜过的点,相当于再进行了一次无用功,因为我们之前已经搜到了。
解决方案
所以我们可以用一个二维数组记录之前搜到过的点,这个点的值就是从该点开始搜的最大值。然后维护这个点就行了。所以当我们搜到过这个点,我们就直接返回这个点的值就行了,不需要再继续搜索了。
考虑从四个方向滑过来的最大值,那么当前值dp(x,y)就等于四个方向滑过来的最大值+1。
将四个方向滑过来的最大值记为dp(nx,ny),则dp(x,y) = dp(nx,ny)+1,再一步步递归进去回溯出答案。
具体代码
#include <iostream>
#include <cstring>
using namespace std;
const int N = 310;
int dp[N][N], g[N][N];
int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0};//四个方向
int n, m, ans;
int dfs(int x, int y){
// 如果这个点已经计算过了,直接返回即可,这就是记忆化搜索
if(dp[x][y] != -1){
return dp[x][y];
}
dp[x][y] = 1;// 如果没有搜到过,那么就初始化为1
//四个方向
for(int i = 0; i < 4; i++){
int nx = x + dx[i], ny = y + dy[i];
//判断这点是否能走
if(nx >= 0 && nx < n && ny >= 0 && ny < m && g[nx][ny] < g[x][y]){
dp[x][y] = max(dp[x][y], dfs(nx, ny) + 1);//更新
}
}
return dp[x][y];//返回计算之后的值
}
int main(){
//为什么我们这里需要初始化为-1?
//因为我们可以通过这个值是否为-1来判断这个点是否被搜索过
//如果我们全部初始化为0的话
//但是计算过的点也有可能答案是0
//所以必须赋值为-1
//也算是记忆化搜索的核心了
memset(dp, -1, sizeof dp);
cin >> n >> m;//输入滑雪场行和列
for(int i = 0; i < n; i++){
for(int j = 0; j < m; j++){
cin >> g[i][j];//读入滑雪场数据
}
}
//因为这个人可以在任意一点开始滑,所以要遍历一遍滑雪场
for(int i = 0; i < n; i++){
for(int j = 0; j < m; j++){
ans = max(ans, dfs(i, j));//更新答案
}
}
cout << ans;
return 0;
}