记忆化搜索
一、记忆化搜索
搜索的低效在于没有能够很好地处理重叠子问题;动态规划虽然比较好地处理了重叠子问题,但是在有些拓扑关系比较复杂的题目面前,又显得无奈。记忆化搜索正是在这样的情况下产生的,它采用搜索的形式和动态规划中递推的思想将这两种方法有机地综合在一起,扬长避短,简单实用,在信息学中有着重要的作用。
用一个公式简单地说:记忆化搜索=搜索的形式+动态规划的思想
二、记忆化搜索的思想
在搜索过程中,会有很多重复计算,如果我们能记录一些状态的答案,就可以减少重复搜索量。
三、记忆化搜索的适用范围
根据记忆化搜索的思想,它是解决重复计算,而不是重复生成,也就是说,这些搜索必须是在搜索扩展路径的过程中分步计算的题目,也就是“搜索答案与路径相关″的题目,而不能是搜索一个路径之后才能进行计算的题目,必须要分步计算,并且搜索过程中,一个搜索结果必须可以建立在同类型问题的结果上,也就是类似于动态规划解决的那种
也就是说,问题的表达,不是单纯生成一个走步方案,而是生成一个走步方案的代价等,而且每走一步,在搜索树图中生成一个新状态,都可以精确计算出到此为止的费用,也就是,可以分步计算,这样才可以套用已经得到的答案。
四、记忆化搜索的核心实现
a.要通过一个表记录已经存储下的搜索结果(二维数组或哈希表)
b.状态表示,若用数组表示,数组的下标表示每种状态
c.在每一状态搜索的开始,高效的使用数组(或哈希表)搜索这个状态是否出现过,如果已经做过,直接调用答案,回溯
d.如果没有,则按正常方法搜索(搜索过程中不断记录搜索结果
五、经典题目
滑雪:
题目:
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更长。事实上,这是最长的一条。
输入的第一行表示区域的行数R和列数C(1 <= R,C <= 100)。下面是R行,每行有C个整数,代表高度h,0<=h<=10000。
输出最长区域的长度。
分析:
此题用dfs解决,但由于起点与终点均不确定,所以需要枚举所有点为起点时的情况,也就是说每个点可能被多次计算,即要用记忆化。定义dp数组记忆化,搜索时传入坐标,如果dp数组值不为0,就返回它的值,搜索时状态转移方程为:
dp[i][j] = max(dp[i-1][j], dp[i][j-1], dp[i+1][j], dp[i][j+1]) + 1
代码:
#include <cstdio>
#include <algorithm>
using namespace std;
int a[105][105], n, m, dp[105][105], ans = 0;
int wayx[4] = { -1, 0, 1, 0 };
int wayy[4] = { 0, -1, 0, 1 };
int dfs(int x, int y) {
if (dp[x][y] > 0) {
return dp[x][y];
}
dp[x][y] = 1;
for (int i = 0; i < 4; i++) {
int dx = x + wayx[i], dy = y + wayy[i];
if (dx > 0 && dx <= n && dy > 0 && dy <= m) {
if (a[dx][dy] < a[x][y]) {
dp[x][y] = max(dp[x][y], dfs(dx, dy) + 1);
}
}
}
return dp[x][y];
}
int main() {
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
scanf("%d", &a[i][j]);
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
ans = max(dfs(i, j), ans);
}
}
printf("%d", ans);
return 0;
}