本篇为小金鱼大佬视频的学习笔记,原视频链接:https://www.bilibili.com/video/BV1r84y1379W?vd_source=726e10ea5b787a300ceada715f64b4bf
基础概念
暴力dfs很多时候仅能过部分测试点,要想将其优化,一般以 dfs -> 记忆化搜索 -> dp 为路线,后续熟悉后可以直接写出dp代码。
- 记忆化搜索:在dfs的基础上,增加一个数组用于记录已经被计算过的值,以减少后续的计算来达到优化效果
- dp:给定一个问题,将其拆分为无数个可解决的子问题,并将子问题的答案记录下来,根据子问题反推出源问题
所谓递归,“递”是从上往下,将问题分解为子问题的过程,“归”是从下向上回溯,将已有答案的子问题合并产生答案的过程,而dp就是只调用“归”,从下向上直接找答案
例题:
本篇以数字三角形为例,分析dfs逐步优化到dp的过程,原题链接:P1216 [USACO1.5] [IOI1994]数字三角形 Number Triangles - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
本题目标是要找到从顶部到底部产生的最大权值。
方法一:暴力递归
由题可知,一个数字只可以走到他下面的两个数,而在数组中该数下标(x,y),则它可以遍历的数字下标为(x + 1,y)和(x + 1,y + 1),要找到最大的权值,因此递归条件为 max(dfs(x + 1, y), dfs(x + 1, y + 1)) + mp[x][y];
其实以前刚开始学递归时,一直理解不了return后面这个式子,现在从数学公式来理解的话还是有点不明白,但是可以 通过递归搜索树来理解 。
#include<iostream>
using namespace std;
int n;
const int N = 1010;
int mp[N][N];
int mem[N][N] = { 0 };
int dfs(int x, int y) {
if (x > n || y > n) return 0;
return max(dfs(x + 1, y), dfs(x + 1, y + 1)) + mp[x][y];
}
//从顶部到底部产生最大权值
int main() {
cin >> n;
for (int i = 0; i < n; i++) {
for (int j = 0; j <= i; j++) {
cin >> mp[i][j];
}
}
cout << dfs(0,0);
return 0;
}
在洛谷上dfs无法通过所有案例,显示tle
方法二:记忆化搜索
在dfs的基础上,加上一个mem数组用于存储已经计算过的值,可以大大减少计算量
#include<iostream>
using namespace std;
int n;
const int N = 1010;
int mp[N][N];
int mem[N][N] = { 0 };
int dfs(int x, int y) {
if (mem[x][y]) return mem[x][y];
int res = 0;
if (x > n || y > n) res = 0;
else res = max(dfs(x + 1, y), dfs(x + 1, y + 1)) + mp[x][y];
mem[x][y] = res;
return res;
}
int main() {
cin >> n;
for (int i = 0; i < n; i++) {
for (int j = 0; j <= i; j++) {
cin >> mp[i][j];
}
}
cout << dfs(0,0);
return 0;
}
也就是说, 记忆化搜索 = dfs + 记录答案
同时要注意:
- 要想实现 记忆化搜索 , dfs参数要尽可能少 ,不应该把没影响边界的参数放进来
- 要想 剪枝 ,就应尽可能把能剪枝的参数写上来
方法三:递推
递推,也就是从已知答案的小问题逐步推演到源问题的过程,也就是从递归搜索树的下面走到上面回溯的过程,而此过程往往用数组来存储值 。(本题为了便于区分不同方法,用f数组来存储)
注意: for循环一定注意是从0增还是从n减 ,而这取决于递归搜索树的底部是n还是0。
#include<iostream>
using namespace std;
int n;
const int N = 1010;
int mp[N][N];
int f[N][N] = { 0 };
//从顶部到底部产生最大权值
int main() {
cin >> n;
for (int i = 0; i < n; i++) {
for (int j = 0; j <= i; j++) {
cin >> mp[i][j];
}
}
for (int i = n - 1; i >= 0; i--) {
for (int j = 0; j < n; j++) {
f[i][j] = max(f[i + 1][j], f[i + 1][j + 1]) + mp[i][j];
}
}
cout << f[0][0];
return 0;
}
也可以看出:
- 递推公式 = dfs向下递归公式
- 递推数组初始值 = dfs递归边界
以上是本文全部内容,如果对你有帮助点个赞再走吧~ ₍˄·͈༝·͈˄*₎◞ ̑̑