Loading docs/basic/divide-and-conquer.md +28 −5 Original line number Diff line number Diff line # 递归 介绍分治之前,首先要弄清楚 [递归](https://zh.wikipedia.org/zh-hans/%E9%80%92%E5%BD%92) 这个概念。 介绍分治之前,首先要弄清楚递归这个概念。 递归是什么呢?举个简单的例子:从前有座山,山上有座庙,庙里有个老和尚,老和尚给小和尚讲故事,讲的什么故事呢?从前有座山,山上有座庙,庙里有个老和尚…… 递归的基本思想是某个函数直接或者间接地调用自身,这样就把原问题的求解转换为许多性质相同但是规模更小的子问题。我们只需要关注如何把原问题划分成符合条件的子问题,而不需要去研究这个子问题是如何被解决的。 Loading @@ -8,20 +10,41 @@ 在用递归思想解题的时候,要考虑三个要素。 ## 递归式 ## 递归三要素 ### 递归式 具体是如何地将原问题划分为子问题? 如何将原问题划分为子问题?如何科学地进行递归? ## 递归出口 ### 递归出口 终止的条件是什么?换言之,最小的子问题是怎么求解的。 出口可以不止一个。 ## 界函数 ### 界函数 我们用一个函数来表示问题规模变化,这个函数需要保证递归的条件是在像出口条件靠拢。 ### 递归模板 ```c++ int f(传入数值) { if (终止条件) return 最小子问题解; return f(缩小规模); } ``` ## 递归优化 先来一道例题:[三连击](https://www.luogu.org/problemnew/show/P1028)。 这道题朴素的递归写法只能得到 25 分,因为递归次数太多,所以超时。 怎么优化呢?详见 [搜索优化](/search/optimization) 和 [记忆化搜索](https://oi-wiki.org/dp/memo/)。 # 分治 分治是一种极为重要的思想。顾名思义,分而治之,就是把大问题化小,再各个击破的过程。 Loading docs/dp/memo.md +27 −14 Original line number Diff line number Diff line Loading @@ -123,9 +123,7 @@ int main(){ #### 总结一下记忆化搜索是啥: - 不依赖任何 **外部变量** - 答案以返回值的形式存在, 而不能以参数的形式存在 (就是不能将 dfs 定义成 $dfs(pos ,tleft , nowans )$, 这里面的 nowans 不符合要求). - 对于相同一组参数, dfs 返回值总是相同的 * * * Loading Loading @@ -236,17 +234,13 @@ dp 状态很显然: 举例: 用常规 dp 写 "合并石子" 需要先枚举区间长度然后枚举起点, 但记忆化搜索直接枚举断点 (就是枚举当前区间由哪两个区间合并而成) 然后递归下去就行 - 边界情况非常好处理, 且能有效防止数组访问越界 - 有些 dp (如区间 dp) 用记忆化搜索写很简单但正常 dp 很难 - 记忆化搜索天生携带搜索天赋, 可以使用技能 "剪枝"! 缺点: - 致命伤: 不能滚动数组! - 有些优化比较难加 - 由于递归, 有时效率较低但不至于 TLE (状压 dp 除外) * * * Loading @@ -254,7 +248,26 @@ dp 状态很显然: ## 5. 记忆化搜索的注意事项 - 千万别忘了加记忆化! (别笑, 认真的 - 边界条件要加在检查当前数组值是否为 - 1 前 (防止越界) - 边界条件要加在检查当前数组值是否为非法数值 (防止越界) - 数组不要开小了 (逃 ## 6. 模板 ```c++ int g[MAXN]; int f(传入数值) { if (g[规模]!=无效数值) return g[规模]; if (终止条件) return 最小子问题解; g[规模]=f(缩小规模); return g[规模]; } int main() { ... memset(g,无效数值,sizeof(g)); ... } ``` docs/search/optimization.md 0 → 100644 +1 −0 Original line number Diff line number Diff line mkdocs.yml +2 −1 Original line number Diff line number Diff line Loading @@ -28,7 +28,7 @@ nav: - 基础部分简介: basic/index.md - 枚举: basic/enumerate.md - 模拟: basic/simulate.md - 分治: basic/divide-and-conquer.md - 递归&分治: basic/divide-and-conquer.md - 贪心: basic/greedy.md - 排序: basic/sort.md - 表达式求值: basic/expression.md Loading @@ -45,6 +45,7 @@ nav: - IDA*: search/idastar.md - 回溯法: search/backtracking.md - Dancing Links: search/dlx.md - 优化: search/optimization.md - 动态规划: - 动态规划部分简介: dp/index.md - 记忆化搜索: dp/memo.md Loading Loading
docs/basic/divide-and-conquer.md +28 −5 Original line number Diff line number Diff line # 递归 介绍分治之前,首先要弄清楚 [递归](https://zh.wikipedia.org/zh-hans/%E9%80%92%E5%BD%92) 这个概念。 介绍分治之前,首先要弄清楚递归这个概念。 递归是什么呢?举个简单的例子:从前有座山,山上有座庙,庙里有个老和尚,老和尚给小和尚讲故事,讲的什么故事呢?从前有座山,山上有座庙,庙里有个老和尚…… 递归的基本思想是某个函数直接或者间接地调用自身,这样就把原问题的求解转换为许多性质相同但是规模更小的子问题。我们只需要关注如何把原问题划分成符合条件的子问题,而不需要去研究这个子问题是如何被解决的。 Loading @@ -8,20 +10,41 @@ 在用递归思想解题的时候,要考虑三个要素。 ## 递归式 ## 递归三要素 ### 递归式 具体是如何地将原问题划分为子问题? 如何将原问题划分为子问题?如何科学地进行递归? ## 递归出口 ### 递归出口 终止的条件是什么?换言之,最小的子问题是怎么求解的。 出口可以不止一个。 ## 界函数 ### 界函数 我们用一个函数来表示问题规模变化,这个函数需要保证递归的条件是在像出口条件靠拢。 ### 递归模板 ```c++ int f(传入数值) { if (终止条件) return 最小子问题解; return f(缩小规模); } ``` ## 递归优化 先来一道例题:[三连击](https://www.luogu.org/problemnew/show/P1028)。 这道题朴素的递归写法只能得到 25 分,因为递归次数太多,所以超时。 怎么优化呢?详见 [搜索优化](/search/optimization) 和 [记忆化搜索](https://oi-wiki.org/dp/memo/)。 # 分治 分治是一种极为重要的思想。顾名思义,分而治之,就是把大问题化小,再各个击破的过程。 Loading
docs/dp/memo.md +27 −14 Original line number Diff line number Diff line Loading @@ -123,9 +123,7 @@ int main(){ #### 总结一下记忆化搜索是啥: - 不依赖任何 **外部变量** - 答案以返回值的形式存在, 而不能以参数的形式存在 (就是不能将 dfs 定义成 $dfs(pos ,tleft , nowans )$, 这里面的 nowans 不符合要求). - 对于相同一组参数, dfs 返回值总是相同的 * * * Loading Loading @@ -236,17 +234,13 @@ dp 状态很显然: 举例: 用常规 dp 写 "合并石子" 需要先枚举区间长度然后枚举起点, 但记忆化搜索直接枚举断点 (就是枚举当前区间由哪两个区间合并而成) 然后递归下去就行 - 边界情况非常好处理, 且能有效防止数组访问越界 - 有些 dp (如区间 dp) 用记忆化搜索写很简单但正常 dp 很难 - 记忆化搜索天生携带搜索天赋, 可以使用技能 "剪枝"! 缺点: - 致命伤: 不能滚动数组! - 有些优化比较难加 - 由于递归, 有时效率较低但不至于 TLE (状压 dp 除外) * * * Loading @@ -254,7 +248,26 @@ dp 状态很显然: ## 5. 记忆化搜索的注意事项 - 千万别忘了加记忆化! (别笑, 认真的 - 边界条件要加在检查当前数组值是否为 - 1 前 (防止越界) - 边界条件要加在检查当前数组值是否为非法数值 (防止越界) - 数组不要开小了 (逃 ## 6. 模板 ```c++ int g[MAXN]; int f(传入数值) { if (g[规模]!=无效数值) return g[规模]; if (终止条件) return 最小子问题解; g[规模]=f(缩小规模); return g[规模]; } int main() { ... memset(g,无效数值,sizeof(g)); ... } ```
mkdocs.yml +2 −1 Original line number Diff line number Diff line Loading @@ -28,7 +28,7 @@ nav: - 基础部分简介: basic/index.md - 枚举: basic/enumerate.md - 模拟: basic/simulate.md - 分治: basic/divide-and-conquer.md - 递归&分治: basic/divide-and-conquer.md - 贪心: basic/greedy.md - 排序: basic/sort.md - 表达式求值: basic/expression.md Loading @@ -45,6 +45,7 @@ nav: - IDA*: search/idastar.md - 回溯法: search/backtracking.md - Dancing Links: search/dlx.md - 优化: search/optimization.md - 动态规划: - 动态规划部分简介: dp/index.md - 记忆化搜索: dp/memo.md Loading