当前位置: 首页 > news >正文

【基础算法】记忆化搜索

文章目录

  • 一、记忆化搜索
    • 1. 什么是记忆化搜索
    • 2. 【案例】斐波那契数
  • 二、OJ 练习
    • 1. Function ⭐
      • (1) 解题思路
      • (2) 代码实现
    • 2. 天下第一 ⭐⭐
      • (1) 解题思路
      • (2) 代码实现
    • 3. 滑雪 ⭐⭐
      • (1) 解题思路
      • (2) 代码实现

一、记忆化搜索

1. 什么是记忆化搜索

记忆化搜索也是一种剪枝策略。通过创建一个 “备忘录”,记录第一次搜索到的结果,当下一次搜索到这个状态时,直接在 “备忘录” 里面找结果,而不用去重新计算。

记忆化搜索,有时候也叫动态规划。


2. 【案例】斐波那契数

【题目链接】

509. 斐波那契数 - 力扣(LeetCode)

这道题十分经典,解决方法也很多。我们很容易写出下面的代码:

class Solution 
{
public:int fib(int n) {if(n == 1 || n == 0) return n;else return fib(n - 1) + fib(n - 2);}
};

但是,这样很容易就超时了,原因在于我们实际上重复计算了很多项。

请添加图片描述

如果不想重复计算,我们可以创建一个数组,下标为 n 的地方用于存储 f(n) 的计算结果,当我们第一次计算某个 f(i) 的时候,计算完成我们就把结果存储在对应的位置,那么当下一次我们再次遇到 f(i) 时就可以直接从里面拿值而非再递归重新计算。这种优化方案就是记忆化搜索。

class Solution 
{int memo[101];
public:int dfs(int n){if(n <= 1) return n;// 如果数组里面的值不是 -1,说明我们已经计算过了,直接拿值返回即可if(memo[n] != -1) return memo[n];// 数组里面的值是 -1, 说明第一次计算,正常递归进去计算memo[n] = dfs(n - 1) + dfs(n - 2);// 计算完之后返回这个值即可return memo[n];}int fib(int n) {// 把数组里的值全部初始化为 -1// 之所以初始化为 -1 而非 0,是因为避免与 f(0) 混淆memset(memo, -1, sizeof(memo));return dfs(n);}
};

二、OJ 练习

1. Function ⭐

【题目链接】

P1464 Function - 洛谷

【题目描述】

对于一个递归函数 w(a,b,c)w(a,b,c)w(a,b,c)

  • 如果 a≤0a \le 0a0b≤0b \le 0b0c≤0c \le 0c0 就返回值 111
  • 如果 a>20a>20a>20b>20b>20b>20c>20c>20c>20 就返回 w(20,20,20)w(20,20,20)w(20,20,20)
  • 如果 a<ba<ba<b 并且 b<cb<cb<c 就返回 w(a,b,c−1)+w(a,b−1,c−1)−w(a,b−1,c)w(a,b,c-1)+w(a,b-1,c-1)-w(a,b-1,c)w(a,b,c1)+w(a,b1,c1)w(a,b1,c)
  • 其它的情况就返回 w(a−1,b,c)+w(a−1,b−1,c)+w(a−1,b,c−1)−w(a−1,b−1,c−1)w(a-1,b,c)+w(a-1,b-1,c)+w(a-1,b,c-1)-w(a-1,b-1,c-1)w(a1,b,c)+w(a1,b1,c)+w(a1,b,c1)w(a1,b1,c1)

这是个简单的递归函数,但实现起来可能会有些问题。当 a,b,ca,b,ca,b,c 均为 151515 时,调用的次数将非常的多。你要想个办法才行。

注意:例如 w(30,−1,0)w(30,-1,0)w(30,1,0) 又满足条件 111 又满足条件 222,请按照最上面的条件来算,答案为 111

【输入格式】

会有若干行。

并以 −1,−1,−1-1,-1,-11,1,1 结束。

【输出格式】

输出若干行,每一行格式:

w(a, b, c) = ans

注意空格。

示例一

1 1 1
2 2 2
-1 -1 -1

示例二

w(1, 1, 1) = 2
w(2, 2, 2) = 4

说明/提示

保证输入的数在 [−9223372036854775808,9223372036854775807][-9223372036854775808,9223372036854775807][9223372036854775808,9223372036854775807] 之间,并且是整数。

保证不包括 −1,−1,−1-1, -1, -11,1,1 的输入行数 TTT 满足 1≤T≤1051 \leq T \leq 10 ^ 51T105


(1) 解题思路

其实这道题就是加强版的斐波那契数,当我们举一个例子画出递归展开图时我们会发现,出现了大量的重复子问题,因此为了避免大量的重复计算,我们需要剪枝,于是可以采用记忆化搜索。

请添加图片描述

我们依旧创建一个备忘录,当发现是第一次计算的时候,我们正常按照规则进行计算并把对应的值存进备忘录中。当发现备忘录中的值已经计算过了时,我们直接拿值返回即可。


(2) 代码实现

#include<iostream>using namespace std;typedef long long LL;
const int N = 25;
int f[N][N][N];  // 备忘录,f[a][b][c] 存储的是 w(a, b, c) 的值LL w(LL a, LL b, LL c)
{if(a <= 0 || b <= 0 || c <= 0) return 1;if(a > 20 || b > 20 || c > 20) return w(20, 20, 20);// 当备忘录中的值非 0,即已经计算过了,直接拿值if(f[a][b][c]) return f[a][b][c];// 下面就是没有计算过的情况// 按照题目中的规则计算然后存入备忘录中即可if(a < b && b < c){f[a][b][c] =  w(a, b, c - 1) + w(a, b - 1, c - 1) - w(a, b - 1, c);return f[a][b][c];}f[a][b][c] = w(a - 1, b, c) + w(a - 1, b - 1, c) + w(a - 1, b ,c - 1) - w(a - 1, b - 1, c - 1);return f[a][b][c];
}int main()
{while(1){LL a, b, c;cin >> a >> b >> c;if(a == -1 && b == -1 && c == -1) break;printf("w(%lld, %lld, %lld) = %lld\n", a, b, c, w(a, b, c));}return 0;
}

2. 天下第一 ⭐⭐

【题目链接】

P5635 【CSGRound1】天下第一 - 洛谷

【题目背景】

天下第一的 cbw 以主席的身份在 8102 年统治全宇宙后,开始了自己休闲的生活,并邀请自己的好友每天都来和他做游戏。由于 cbw 想要显出自己平易近人,所以 zhouwc 虽然是一个蒟蒻,也有能和 cbw 玩游戏的机会。

【题目描述】

游戏是这样的:

给定两个数 xxxyyy,与一个模数 ppp

cbw 拥有数 xxx,zhouwc 拥有数 yyy

第一个回合:x←(x+y)modpx\leftarrow(x+y)\bmod px(x+y)modp

第二个回合:y←(x+y)modpy\leftarrow(x+y)\bmod py(x+y)modp

第三个回合:x←(x+y)modpx\leftarrow(x+y)\bmod px(x+y)modp

第四个回合:y←(x+y)modpy\leftarrow(x+y)\bmod py(x+y)modp

以此类推…

如果 xxx 先到 000,则 cbw 胜利。如果 yyy 先到 000,则 zhouwc 胜利。如果 x,yx,yx,y 都不能到 000,则为平局。

cbw 为了捍卫自己主席的尊严,想要提前知道游戏的结果,并且可以趁机动点手脚,所以他希望你来告诉他结果。

【输入格式】

有多组数据。

第一行:TTTppp 表示一共有 TTT 组数据且模数都为 ppp

以下 TTT 行,每行两个数 x,yx,yx,y

【输出格式】

TTT

111 表示 cbw 获胜,222 表示 zhouwc 获胜,error表示平局。

示例一

输入

1 10
1 3

输出

error

【示例二】

输入

1 10
4 5

输出

1

【说明/提示】

1≤T≤2001 \leq T \leq 2001T200

1≤x,y,p≤100001 \leq x,y,p \leq 100001x,y,p10000


(1) 解题思路

我们可以把递归函数 dfs 的返回值设置为 char,返回值为 '1' 表示 cbw 胜,返回值为 '2' 表示 zhouwc 胜,'e' 表示平局。传入参数 xy,那么只要 x 为 0 就返回 ’1‘,只要 y 为 0 就返回 '2'。如果二者都不为 0,那么就递归到下一轮,根据模运算的性质,我们可以直接返回 dfs((x + y) % p, (x + y + y) % p)

但是这样遇到平局的话就会出现死循环,于是我们需要采用记忆化搜索的方式来发现这一个重复的情况。我们可以创建一个备忘录 char f[N][N],值为 '1' 表示 cbw 胜,值为 '2' 表示 zhouwc 胜,'e' 表示平局。初始化为空字符,只要走到一个位置,就把对应位置设置为 'e',如果再次遇到,说明这局必定为平局,那么返回这个 'e' 即可。


(2) 代码实现

#include<iostream>using namespace std;const int N = 1e4 + 10;
int x, y, p;// 备忘录f[x][y]记录对应值为 x, y 时的胜负情况
// '1': cbw胜  '2': zhouwc胜  'e': 平局
char f[N][N];
// 用 char 原因是防止爆空间char dfs(int x, int y)
{// 如果备忘录里有值,直接拿值if(f[x][y]) return f[x][y];// 只要第一次计算,就先把它设置成平局,这样再次遇到时,就可以返回 'e' 平局的信息f[x][y] = 'e';// 而如果这时候有一个变成 0 了,就把备忘录中的信息修改正确了再返回if(x == 0) return f[x][y] = '1';if(y == 0) return f[x][y] = '2';// 还没有分出胜负就继续return f[x][y] = dfs((x + y) % p, (x + y + y) % p);
}int main()
{int T;cin >> T >> p;while(T--){cin >> x >> y;char ret = dfs(x, y);if(ret == '1') cout << '1' << endl;if(ret == '2') cout << '2' << endl;if(ret == 'e') cout << "error" << endl;}return 0;
}

3. 滑雪 ⭐⭐

【题目描述】

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−124-17-16-12417161(从 242424 开始,在 111 结束)。当然 252525242424232323…\ldots333222111 更长。事实上,这是最长的一条。

【输入格式】

输入的第一行为表示区域的二维数组的行数 RRR 和列数 CCC。下面是 RRR 行,每行有 CCC 个数,代表高度(两个数字之间用 111 个空格间隔)。

【输出格式】

输出区域中最长滑坡的长度。

示例一

输入

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\%100% 的数据,1≤R,C≤1001\leq R,C\leq 1001R,C100


(1) 解题思路

暴力枚举,遍历整个矩阵,看看以当前位置为起点,最远能滑行多远的距离。在所有情况里面,取最大值即可。

如何计算从某个位置 [x, y] 开始的最远滑行距离?只需直到我们当前位置的上下左右四个位置(如果存在的话)能滑行的最远距离的最大值然后 + 1 即可。即大致思路为:

dfs(x, y) = max(dfs(x - 1, y), dfs(x + 1, y), dfs(x, y - 1), dfs(x, y + 1)) + 1

当然这其中需要判断有没有上下左右,并且只有高度比当前位置低才能到该位置。更重要的是,我们在计算的过程中,某个位置能滑行的最远距离一定是确定的,当我们计算了一次之后就可以保存下来不必再次递归计算。因此我们可以采用记忆化搜索进行优化。


(2) 代码实现

#include<iostream>using namespace std;
const int N = 105;
int h[N][N];  // 记录高度
int f[N][N];  // 备忘录
int r, c;// 从 [x, y] 位置开始能滑行的最远距离
int dfs(int x, int y)
{// 如果计算过了,直接拿值返回即可if(f[x][y]) return f[x][y];int t = 1;  // 记录最远距离,最小为 1// 上if(y > 0 && h[x][y - 1] < h[x][y]) t = max(t, dfs(x, y - 1) + 1);// 下if(y < c - 1 && h[x][y + 1] < h[x][y]) t = max(t, dfs(x, y + 1) + 1);// 左if(x > 0 && h[x - 1][y] < h[x][y]) t = max(t, dfs(x - 1, y) + 1);// 右if(x < r - 1 && h[x + 1][y] < h[x][y]) t = max(t, dfs(x + 1, y) + 1);f[x][y] = t;return f[x][y];
}int main()
{cin >> r >> c;for(int i = 0; i < r; i++)for(int j = 0; j < c; j++)cin >> h[i][j];int res = -1;for(int i = 0; i < r; i++)for(int j = 0; j < c; j++)res = max(res, dfs(i, j));cout << res << endl;return 0;
}
http://www.dtcms.com/a/473654.html

相关文章:

  • wordpress yum上海搜索引擎优化公司排名
  • c++类和对象(下)
  • 算法7.0
  • 【异常处理——下】
  • axios请求
  • 109、23种设计模式之迭代器模式(18/23)
  • 餐饮设计公司网站wordpress如何保存
  • 前端页面出现问题ResizeObserver loop completed with undelivered notifications.
  • 有声阅读网站如何建设邵阳学院研究生与学科建设处网站
  • AWS RDS Aurora MySQL高CPU使用率问题诊断与解决实战
  • 【Swift】LeetCode 11. 盛最多水的容器
  • 设计模式之 享元模式 Flyweight
  • 智械觉醒当AI开始思考“我是谁”
  • 商河 网站建设公司网站的具体的建设方案
  • 湖南省网站备案婚纱摄影网站应该如何做优化
  • pytest学习
  • seo网站建设厦门百度广告代理商查询
  • 【全连接神经网络】基本原理
  • Go 异步编程
  • 基于贪心最小化包围盒策略的布阵算法
  • 《Python 异步数据库访问全景解析:从阻塞陷阱到高性能实践》
  • AI 自己造“乐高积木”:生成式 AI 设计可拼装模块化硬件的实战笔记
  • 10.11笔记
  • 冒泡排序的多种实现方式详解
  • 网页设计平面设计温州网站优化页面
  • 特别分享:聊聊Git
  • M|蝙蝠侠:侠影之谜
  • crawl4ai智能爬虫(一):playwright爬虫框架详解
  • 探究Java、C语言、Python、PHP、C#与C++在多线程编程中的核心差异与应用场景
  • 国外网站模板网站建设ui培训班好