Unverified Commit 8254ec19 authored by interestingLSY's avatar interestingLSY Committed by GitHub
Browse files

Merge pull request #2 from interestingLSY/interestingLSY-patch-1

Update memo.md更新记忆化搜索
parents 3e7bd562 f3677fa2
Loading
Loading
Loading
Loading
+90 −21
Original line number Diff line number Diff line
# 聊聊动态规划与记忆化搜索

by $\color{Gray}InterestingLSY$ (菜到发灰)
by $\color{Gray}{InterestingLSY}$ (菜到发灰)

> 想体验把暴搜改改就是正解的快感吗? 想体验状压dp看似状态多到爆炸实际一跑却嗷嗷快(实际有效的状态数很少)的荣耀吗? 记忆化搜索,符合您的需求!只要998,记忆化搜索带回家!记忆化搜索,记忆化搜索,再说一遍,记忆化搜索!

*由于我讲的比较磨叽,兜售目录一份,dalao们可以选择自己喜欢的部分看*

## 目录:

- 记忆化搜索是啥

- 记忆化搜索和动态规划有啥关系

- 如何写记忆化搜索

- 记忆化搜索的优缺点

- 记忆化搜索的注意事项

---

## 1. 记忆化搜索是啥

@@ -32,7 +50,7 @@ int main(){
```
这就是个十分智障的大暴搜是吧......

emmmmmm....... $\color{Red}30$ 分
emmmmmm....... $\color{Red}{30}$ 分

然后我心血来潮, 想不借助任何 "外部变量"(就是 dfs 函数外且 ** 值随 dfs 运行而改变的变量 **), 比如 ans

@@ -67,7 +85,7 @@ int main(){
}
```

~~emmmmmm....... 还是 $\color{Red}30$ 分~~
~~emmmmmm....... 还是 $\color{Red}{30}$ 分~~

但这个时候, 我们的程序已经不依赖任何外部变量了.

@@ -122,19 +140,37 @@ int main(){

- 不依赖任何 ** 外部变量 **

- 答案以返回值的形式存在, 而不能以参数的形式存在(就是不能将 dfs 定义成 $dfs(pos ,tleft , nowans )$, 这里面的 nowans 不符合要求.
- 答案以返回值的形式存在, 而不能以参数的形式存在(就是不能将 dfs 定义成 $dfs(pos ,tleft , nowans )$, 这里面的 nowans 不符合要求).

- 对于相同一组参数, dfs 返回值总是相同的

---

## 2. 记忆化搜索与动态规划的关系:

~~基本是朋 (ji) 友关系~~

时间复杂度 / 空间复杂度与 ** 不加优化的 dp** 完全相同
有人会问: 记忆化搜索难道不是搜索?

是搜索.但个人认为她更像dp:

不信你看$mem$ 的意义:

> 在时间 $tleft$ 内采集 **后** $pos$ 个草药, 能获得的最大收益

这不就是dp的状态?

由上面的代码中可以看出:

> $mem[pos][tleft] = max(mem[pos+1][tleft-tcost[pos]]+mget[pos]\ ,\ mem[pos+1][tleft])$

这不就是dp的状态转移?

个人认为:

不管定义咋扯, 反正我觉得
> 记忆化搜索约等于动态规划,**(印象中)任何一个 dp 方程都能转为记忆化搜索 **

> 记忆化搜索就是动态规划,**(印象中)任何一个 dp 方程都能转为记忆化搜索 **
大部分记忆化搜索的状态/转移方程与dp都一样,时间复杂度 / 空间复杂度与 ** 不加优化的 ** dp 完全相同

比如:

@@ -155,6 +191,18 @@ int main(){
}
```

---

## 3. 如何写记忆化搜索

### 方法I:

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

举例:

$dp[i] = max\{dp[j]+1\}\quad 1 \leq j < i \text{且}a[j]<a[i]$  (最长上升子序列)

转为
@@ -174,40 +222,59 @@ int main(){
    cout << dfs(n) << endl;
}
```
### 方法II:

**当然, 以我的经验更多情况下记忆化搜索是写完暴力 dfs(本来想骗分)后突然发现能改成记忆化搜索**
1. 写出这道题的暴搜程序(最好是dfs)
2. 将这个dfs改成"无需外部变量"的dfs
3. 添加记忆化数组

~~ 然后 AC 了一道全场没几个人会的超难 dp~~
举例: 本文最开始介绍"什么是记忆化搜索"时举的"采药"那题的例子

感受以下那种发现自己写的暴力改改就是正解的快感吧!
---

啊哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈!

## 3. 记忆化搜索的优缺点

咳咳, 说正事
## 4. 记忆化搜索的优缺点

优点:

- 记忆化搜索可以避免搜到无用状态, 特别是在有状态压缩时

举例: 给你一个有向图(注意不是完全图),经过每条边都有花费,求从点1出发,经过每个点**恰好一次**后的最小花费(最后不用回到起点),保证路径存在.

dp状态很显然:

设 $dp[pos][mask]$ 表示身处在 $pos$ 处,走过 $mask$(mask为一个二进制数) 中的顶点后的最小花费

常规 $dp$ 的状态为 $O(n\cdot 2^n)$ , 转移复杂度(所有的加在一起)为 $O(m)$

但是!如果我们用记忆化搜索,就可以避免到很多无用的状态,比如 $pos$ 为起点却已经经过了 $>1$ 个点的情况.

然后就 $rk1$ 了

- 不需要注意转移顺序(这里的"转移顺序"指正常dp中for循环的嵌套顺序以及循环变量是递增还是递减)

举例: 用常规 dp 写"合并石子"需要先枚举区间长度然后枚举起点,但记忆化搜索直接枚举断点(就是枚举当前区间由哪两个区间合并而成)然后递归下去就行

- 边界情况非常好处理, 且能有效防止数组访问越界

- ~~写起来简单易懂~~ 至少我镇么认为 qwq

- 有些 dp(如区间 dp)用记忆化搜索写很简单但正常 dp 很难

- 记忆化搜索天生携带搜索天赋,可以使用技能"剪枝"!

缺点:

- 致命伤: 不能滚动数组!(哪位 dalao 会记搜 + 滚动的请在评论区留名)

- 有些优化比较难加

- 由于递归, 有时效率较低但不至于 TLE
- 由于递归, 有时效率较低但不至于 TLE (状压dp除外)

- 代码有点长~~其实也不算太长~~

## 4. 记忆化搜索的注意事项:
---

## 5. 记忆化搜索的注意事项

- 千万别忘了加记忆化! (别笑, 认真的

@@ -215,6 +282,8 @@ int main(){

- 数组不要开小了(逃

---

## 如有疑问或质疑, 请留下评论或私信我

** questions are welcome **