「学习笔记」记忆化搜索

这篇具有很好参考价值的文章主要介绍了「学习笔记」记忆化搜索。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

由于我一直对搜索情有独钟,因此,如果能写记忆化搜索的绝不会写 for 循环 DP。
文章部分内容来自 \(\texttt{OI-Wiki}\)

引入

记忆化搜索是一种通过记录已经遍历过的状态的信息,从而避免对同一状态重复遍历的搜索实现方式。
因为记忆化搜索确保了每个状态只访问一次,它也是一种常见的动态规划实现方式。

我们通过下面一道题来引入。

P1434 [SHOI2002] 滑雪
有一个 \(R \times C\) 的二维矩阵,可以从某个点到达上下左右相邻四个点之一,当且仅当高度会减小,即这是一个下降序列。求这个下降序列长度的最大值。

我们的朴素 DFS 做法:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

const int fx[4] = {0, 1, 0, -1};
const int fy[4] = {1, 0, -1, 0};

int r, c, maxn;
int a[110][110], f[110][110];

int dfs(int x, int y) {
	int xx, yy, ma = 0;
	for (int i = 0; i <= 3; ++i) {
		xx = x + fx[i];
		yy = y + fy[i];
		if (xx > 0 && xx <= r && yy > 0 && yy <= c && a[xx][yy] < a[x][y]) {
			ma = max(dfs(xx, yy), ma);
		}
	}
	return ma + 1;
}

int main() {
	r = read(), c = read();
	for (int i = 1; i <= r; ++ i)
		for (int j = 1; j <= c; ++ j) {
			a[i][j] = read();
		}
	for (int i = 1; i <= r; ++ i)
		for (int j = 1; j <= c; ++ j) {
			maxn = max(maxn, dfs(i, j));
		}
	printf("%d", maxn);
	return 0;
}

交上去一看,T 了一个点。
为什么 T 了呢?
我们假设 \((i, j)\) 这个点当前被搜到,继续搜,得到最大值,返回了。
后来,又一次搜到了 \((i, j)\) 这个点,然后又重新搜了一遍;再后来,又搜到了这个点,又重新搜了一遍......
因此,导致我们的这份代码跑得慢的原因就是多次进行同一个操作,搜索同一个变量。
为了提升速度,防止重复搜一种情况,我们设置记忆化数组来存储我们的值,同时阻止他继续重复搜索。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

const int fx[4] = {0, 1, 0, -1};
const int fy[4] = {1, 0, -1, 0};

int r, c, maxn;
int a[110][110], f[110][110];

int dfs(int x, int y) {
	if (f[x][y])	return f[x][y];
	f[x][y] = 1;
	int xx, yy, ma = 0;
	for (int i = 0; i <= 3; ++i) {
		xx = x + fx[i];
		yy = y + fy[i];
		if (xx > 0 && xx <= r && yy > 0 && yy <= c && a[xx][yy] < a[x][y]) {
			ma = max(dfs(xx, yy), ma);
		}
	}
	f[x][y] += ma;
	return f[x][y];
}

int main() {
	r = read(), c = read();
	for (int i = 1; i <= r; ++ i) {
		for (int j = 1; j <= c; ++ j) {
			a[i][j] = read();
		}
	}	
	for (int i = 1; i <= r; ++ i) {
		for (int j = 1; j <= c; ++ j) {
			maxn = max(maxn, dfs(i, j));
		}
	}
	printf("%d\n", maxn);
	return 0;
}

然后,你就可以愉快的 AC 了!
由此你也发现了,记忆化搜索相较于一般搜索速度快是因为避免了对同一状态的重复遍历。

写记忆化搜索方法

方法一

  1. 把这道题的 dp 状态和方程写出来
  2. 根据它们写出 dfs 函数
  3. 添加记忆化数组

方法二

  1. 写出这道题的暴搜程序(最好是 dfs)
  2. 将这个 dfs 改成无需外部变量的 dfs
  3. 添加记忆化数组

与递推的区别

记忆化搜索和递推,都确保了同一状态至多只被求解一次。而它们实现这一点的方式则略有不同:递推通过设置明确的访问顺序来避免重复访问,记忆化搜索虽然没有明确规定访问顺序,但通过给已经访问过的状态打标记的方式,也达到了同样的目的。
与递推相比,记忆化搜索因为不用明确规定访问顺序,在实现难度上有时低于递推,且能比较方便地处理边界情况,这是记忆化搜索的一大优势。但与此同时,记忆化搜索难以使用滚动数组等优化,且由于存在递归,运行效率会低于递推。因此应该视题目选择更适合的实现方式。

题目

P1220 关路灯
在一条路线上安装了 \(n\) 盏路灯,每盏灯的功率有大有小(即同一段时间内消耗的电量有多有少)。老张就住在这条路中间某一路灯旁,他有一项工作就是每天早上天亮时一盏一盏地关掉这些路灯。他每天都是在天亮时首先关掉自己所处位置的路灯,然后可以向左也可以向右去关灯。现在已知老张走的速度为 \(1m/s\),每个路灯的位置(是一个整数,即距路线起点的距离,单位:\(m\))、功率(\(W\)),老张关灯所用的时间很短而可以忽略不计。问:怎样最省电?\(n \le 50\)

记忆化搜索代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N = 60;

int n, c;
int s[N], w[N], sum[N];
int dp[N][N][2];

int dfs(int l, int r, int las) {
	if (~dp[l][r][las]) {
		return dp[l][r][las];
	}
	if (l == 1 && r == n) {
		return dp[l][r][las] = 0;
	}
	int minn = 1e9 + 5;
	if (las == 0) {
		if (l != 1 && r != n) {
			minn = min(dfs(l - 1, r, las) + (sum[n] - sum[r] + sum[l - 1])
				* (s[l] - s[l - 1]), dfs(l, r + 1, las ^ 1) + (sum[n] - 
					sum[r] + sum[l - 1]) * (s[r + 1] - s[l]));
		}
		else if (l != 1 && r == n) {
			minn = min(minn, dfs(l - 1, r, las) + (sum[n] - sum[r] + 
				sum[l - 1]) * (s[l] - s[l - 1]));
		}
		else if (l == 1 && r != n) {
			minn = min(minn, dfs(l, r + 1, las ^ 1) + (sum[n] - sum[r]
				+ sum[l - 1]) * (s[r + 1] - s[l]));
		}
	}
	else {
		if (l != 1 && r != n) {
			minn = min(dfs(l - 1, r, las ^ 1) + (sum[n] - sum[r] + sum[l - 1])
				* (s[r] - s[l - 1]), dfs(l, r + 1, las) + (sum[n] - 
					sum[r] + sum[l - 1]) * (s[r + 1] - s[r]));
		}
		else if (l != 1 && r == n) {
			minn = min(minn, dfs(l - 1, r, las ^ 1) + (sum[n] - sum[r] + 
				sum[l - 1]) * (s[r] - s[l - 1]));
		}
		else if (l == 1 && r != n) {
			minn = min(minn, dfs(l, r + 1, las) + (sum[n] - sum[r]
				+ sum[l - 1]) * (s[r + 1] - s[r]));
		}
	}
	return (dp[l][r][las] = minn);
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	memset(dp, -1, sizeof dp);
	cin >> n >> c;
	for (int i = 1; i <= n; ++ i) {
		cin >> s[i] >> w[i];
		sum[i] = sum[i - 1] + w[i];
	}
	cout << min(dfs(c, c, 0), dfs(c, c, 1)) << '\n';
	return 0;
}

P3607 [USACO17JAN]Subsequence Reversal P
FJ 正在安排他的 \(N\) 头奶牛排成一队以拍照 \((1 \le n \le 50)\)。队列中的第i头奶牛的身高为 \(a_i\),并且 FJ 认为如果奶牛的身高序列中含有一个很长的不下降子序列的话,这会是一张很好的照片。
回忆一下,子序列是由牛序列中的一些元素 \(a_{i_1},a_{i_2},.....a_{i_k}\) 组成的子集。\((i_1<i_2< \cdots <i_k)\) 如果 \(a_{i_1} \le a_{i_2} \le a_{i_3} \le \cdots \le a_{i_k}\) 的话,我们就说这个序列是不下降的。
FJ 想要在他的奶牛序列中包括一个长期增长的子序列(也就是很长的不下降子序列)。为了确保这一点,他允许自己在一开始选择任何子序列并逆转其元素。
观察这个子序列(上方英文)是如何反转并占据他们最初的相同的位置的,且其他元素保持不变。
在只能反转一次任意子序列的情况下,请找到不下降子序列的最大可能长度。文章来源地址https://www.toymoban.com/news/detail-481175.html

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N = 110;

int n;
int a[N], dp[60][60][60][60];
bool vis[60][60][60][60];

int dfs(int l, int r, int d, int u) {
	if (vis[l][r][d][u]) {
		return dp[l][r][d][u];
	}
	if (d > u)	return -1e8;
	if (l > r)	return 0;
	if (l == r) {
		if (d <= a[l] && a[r] <= u)	return dp[l][r][d][u] = 1;
		else	return 0;
	}
	dp[l][r][d][u] = max(dp[l][r][d][u], dfs(l + 1, r - 1, d, u));
	if (a[r] >= d) {
		dp[l][r][d][u] = max(dp[l][r][d][u], dfs(l + 1, r - 1, a[r], u) + 1);
	}
	if (a[l] <= u) {
		dp[l][r][d][u] = max(dp[l][r][d][u], dfs(l + 1, r - 1, d, a[l]) + 1);
	}
	if (a[l] <= u && a[r] >= d) {
		dp[l][r][d][u] = max(dp[l][r][d][u], dfs(l + 1, r - 1, a[r], a[l]) + 2);
	}
	dp[l][r][d][u] = max(dfs(l + 1, r, d, u), dp[l][r][d][u]);
	if (a[l] >= d) {
		dp[l][r][d][u] = max(dp[l][r][d][u], dfs(l + 1, r, a[l], u) + 1);
	}
	dp[l][r][d][u] = max(dfs(l, r - 1, d, u), dp[l][r][d][u]);
	if (a[r] <= u) {
		dp[l][r][d][u] = max(dp[l][r][d][u], dfs(l, r - 1, d, a[r]) + 1);
	}
	vis[l][r][d][u] = 1;
	return dp[l][r][d][u];
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	cin >> n;
	for (int i = 1; i <= n; ++ i) {
		cin >> a[i];
	}
	cout << dfs(1, n, 0, 50) << '\n';
	return 0;
}

到了这里,关于「学习笔记」记忆化搜索的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • DFS:记忆化搜索

    . - 力扣(LeetCode)

    2024年04月09日
    浏览(71)
  • 【算法专题】记忆化搜索

    什么是记忆化搜索呢?记忆化搜索其实就是带了\\\"备忘录\\\"的递归,给递归加上一个\\\"备忘录\\\",递归每次返回的时候,将结果放到\\\"备忘录\\\"里面,在每次进入递归的时候,往\\\"备忘录\\\"里面看看,当前需要递归的数据时候在\\\"备忘录\\\"里存在,如果存在,那么就可以直接取此次的结果,

    2024年02月03日
    浏览(41)
  • 记忆化搜索

    目录 一、前言 二、简要谈谈记忆化搜索 三、最长滑雪道 1、题目 2、基本思路 3、python代码 四、立方体IV 1、上链接 2、基本思路 3、C++代码 4、python代码 5、发现的C++与python之间的输入区别 五、食物链 1、上链接 2、基本思路 3、C++代码(7/10) 4、python代码(9/10) 对于学计算机

    2024年02月10日
    浏览(26)
  • 掷骰子(从暴力搜索 到 记忆化搜索 到 动态规划)

    LeetCode上的 掷骰子模拟 题目描述通常如下: 题目:掷骰子模拟(Simulation of Dice Rolling) 原题链接 描述: 有一个骰子模拟器,每次投掷时都会生成一个1到6的随机数。不过,在使用这个模拟器时有一个约束条件:连续掷出数字i的次数不能超过rollMax[i](其中i从1开始编号)。

    2024年03月19日
    浏览(63)
  • DFS(基础,回溯,剪枝,记忆化)搜索

    DFS(深度优先搜索) 基于递归求解问题,而针对搜索的过程 对于问题的介入状态叫初始状态,要求的状态叫目标状态 这里的搜索就是对实时产生的状态进行分析检测,直到得到一个目标状态或符合要求的最佳状态为止。对于实时产生新的状态的过程叫扩展 搜索的要点: 1.选定初

    2024年04月12日
    浏览(36)
  • 周赛379(排序、分类讨论、记忆化搜索(动态规划))

    简单 给你一个下标从 0 开始的二维整数数组 dimensions 。 对于所有下标 i ( 0 = i dimensions.length ), dimensions[i][0] 表示矩形 i 的长度,而 dimensions[i][1] 表示矩形 i 的宽度。 返回对角线最 长 的矩形的 面积 。如果存在多个对角线长度相同的矩形,返回面积最 大 的矩形的面积。

    2024年01月19日
    浏览(43)
  • 跳跃游戏 (DFS->记忆化搜索->动态规划/贪心证明)

            跳跃游戏是一种典型的算法题目,经常是给定一数组arr,从数组的某一位置i出发,根据一定的跳跃规则,比如从i位置能跳arr[i]步,或者小于arr[i]步,或者固定步数,直到到达某一位置,可能是数组的最后一个位置,也有可能是某一特别的数值处,也有可能在这个

    2024年02月03日
    浏览(39)
  • [动态规划及递归记忆搜索法]1.钢条切割问题

    摘要 本系列从6道经典的动态规划题入手,去理解动态规划的基本思路和想法,以及动态规划和递归记忆搜索法存在的某些联系,对于每道题目,我们将用两种方法去实现,这里讲解第一道题目,作个开头。 前言 我们知道,大部分的动态规划题目可以利用递归加记忆搜索法去

    2024年02月03日
    浏览(47)
  • 【LeetCode:72. 编辑距离 | 暴力递归=>记忆化搜索=>动态规划 】

    🚀 算法题 🚀 🌲 算法刷题专栏 | 面试必备算法 | 面试高频算法 🍀 🌲 越难的东西,越要努力坚持,因为它具有很高的价值,算法就是这样✨ 🌲 作者简介:硕风和炜,CSDN-Java领域新星创作者🏆,保研|国家奖学金|高中学习JAVA|大学完善JAVA开发技术栈|面试刷题|面经八股文

    2024年02月06日
    浏览(67)
  • 【动态规划】【记忆化搜索】C++算法:546移除盒子

    视频算法专题 动态规划汇总 记忆化搜索 给出一些不同颜色的盒子 boxes ,盒子的颜色由不同的正数表示。 你将经过若干轮操作去去掉盒子,直到所有的盒子都去掉为止。每一轮你可以移除具有相同颜色的连续 k 个盒子(k = 1),这样一轮之后你将得到 k * k 个积分。 返回 你能

    2024年01月16日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包