From 1cf2032092560c1f8a20cdb485ee36163295ede1 Mon Sep 17 00:00:00 2001 From: 24OI-bot <15963390+24OI-bot@users.noreply.github.com> Date: Tue, 15 Jan 2019 19:35:47 +0800 Subject: [PATCH] style: format markdown files with remark-lint --- docs/basic/binary.md | 14 +- docs/basic/divide-and-conquer.md | 4 +- docs/basic/expression.md | 13 +- docs/basic/file-operation.md | 18 +- docs/basic/greedy.md | 38 +- docs/basic/prefix-sum.md | 34 +- docs/basic/simulate.md | 6 +- docs/basic/sort.md | 52 +- docs/dp/backpack.md | 12 +- docs/dp/dag.md | 15 +- docs/dp/index.md | 95 +- docs/dp/interval.md | 24 +- docs/dp/memo.md | 120 +- docs/dp/number.md | 22 +- docs/dp/optimization.md | 97 +- docs/dp/state.md | 6 +- docs/dp/tree.md | 8 +- docs/ds/balanced-in-seg.md | 12 +- docs/ds/bit.md | 32 +- docs/ds/block-list.md | 10 +- docs/ds/bst.md | 2 +- docs/ds/dividing.md | 23 +- docs/ds/dsu.md | 24 +- docs/ds/hash.md | 8 +- docs/ds/heap.md | 24 +- docs/ds/lct.md | 172 +- docs/ds/monotonous-queue.md | 6 +- docs/ds/monotonous-stack.md | 4 +- docs/ds/pb-ds/priority-queue.md | 59 +- docs/ds/persistent-balanced.md | 46 +- docs/ds/persistent-in-bit.md | 18 +- docs/ds/persistent-seg.md | 15 +- docs/ds/persistent-trie.md | 4 +- docs/ds/queue.md | 8 +- docs/ds/scapegoat.md | 14 +- docs/ds/seg-in-seg.md | 10 +- docs/ds/segment.md | 82 +- docs/ds/sparse-table.md | 50 +- docs/ds/splay.md | 59 +- docs/ds/square-root-decomposition.md | 24 +- docs/ds/stack.md | 28 +- docs/ds/stl.md | 32 +- docs/ds/stl/bitset.md | 56 +- docs/ds/stl/map.md | 57 +- docs/ds/stl/priority_queue.md | 10 +- docs/ds/stl/vector.md | 88 +- docs/ds/treap.md | 12 +- docs/ds/virtual-tree.md | 44 +- docs/ds/wblt.md | 4 +- docs/geometry/2d.md | 65 +- docs/geometry/convex-hull.md | 12 +- docs/geometry/pick.md | 12 +- docs/graph/2-sat.md | 16 +- docs/graph/basic.md | 28 +- docs/graph/bcc.md | 16 +- docs/graph/bi-graph.md | 4 +- docs/graph/bridge.md | 20 +- docs/graph/dag.md | 10 +- docs/graph/differential-constraints.md | 36 +- docs/graph/flow.md | 39 +- docs/graph/flow/node.md | 6 +- docs/graph/heavy-light-decomposition.md | 64 +- docs/graph/index.md | 30 +- docs/graph/lca.md | 29 +- docs/graph/min-circle.md | 26 +- docs/graph/mst.md | 70 +- docs/graph/scc.md | 14 +- docs/graph/shortest-path.md | 82 +- docs/graph/topo.md | 29 +- docs/graph/traverse.md | 8 +- docs/graph/tree-basic.md | 18 +- docs/graph/tree-misc.md | 4 +- docs/index.md | 18 +- docs/intro/about.md | 24 +- docs/intro/common-mistakes.md | 19 +- docs/intro/docker-deploy.md | 8 +- docs/intro/editors.md | 179 +- docs/intro/faq.md | 80 +- docs/intro/judgers.md | 34 +- docs/intro/mode.md | 52 +- docs/intro/non-traditional.md | 14 +- docs/intro/resources.md | 88 +- docs/intro/spj.md | 20 +- docs/intro/testlib/checker.md | 8 +- docs/intro/testlib/general.md | 114 +- docs/intro/testlib/generator.md | 36 +- docs/intro/testlib/index.md | 14 +- docs/intro/testlib/interactor.md | 8 +- docs/intro/testlib/validator.md | 10 +- docs/intro/wsl.md | 96 +- docs/math/base.md | 18 +- docs/math/bezouts.md | 34 +- docs/math/bignum.md | 42 +- docs/math/bit.md | 42 +- docs/math/bsgs.md | 55 +- docs/math/cantor.md | 16 +- docs/math/catalan.md | 24 +- docs/math/combination.md | 51 +- docs/math/complex.md | 32 +- docs/math/crt.md | 56 +- docs/math/dictionary.md | 12 +- docs/math/du-sieves.md | 48 +- docs/math/euler.md | 24 +- docs/math/expectation.md | 24 +- docs/math/fermat.md | 44 +- docs/math/fft.md | 79 +- docs/math/fwt.md | 44 +- docs/math/game-theory.md | 14 +- docs/math/gauss.md | 20 +- docs/math/gcd.md | 72 +- docs/math/inclusion-exclusion-principle.md | 16 +- docs/math/index.md | 34 +- docs/math/inverse.md | 30 +- docs/math/lagrange-poly.md | 12 +- docs/math/linear-equation.md | 12 +- docs/math/matrix.md | 28 +- docs/math/misc.md | 88 +- docs/math/mobius.md | 60 +- docs/math/ntt.md | 38 +- docs/math/prime.md | 40 +- docs/math/quick-pow.md | 22 +- docs/math/sieve.md | 13 +- docs/math/stirling.md | 4 +- docs/misc/cdq-divide.md | 193 +-- docs/misc/complexity.md | 44 +- docs/misc/discrete.md | 4 +- docs/misc/distance.md | 26 +- docs/misc/dsu-on-tree.md | 24 +- docs/misc/endianness.md | 10 +- docs/misc/fractional-programming.md | 12 +- docs/misc/hill-climbing.md | 6 +- docs/misc/index.md | 8 +- docs/misc/io.md | 36 +- docs/misc/magic.md | 20 +- docs/misc/matrix-tree.md | 36 +- docs/misc/mo-algo.md | 116 +- docs/misc/simulated-annealing.md | 12 +- docs/search/astar.md | 34 +- docs/search/bfs.md | 24 +- docs/search/dfs.md | 10 +- docs/search/idastar.md | 19 +- docs/search/index.md | 13 +- docs/search/iterative.md | 4 +- docs/search/optimization.md | 4 +- docs/string/ac-automaton.md | 12 +- docs/string/hash.md | 24 +- docs/string/index.md | 10 +- docs/string/lib-func.md | 52 +- docs/string/manacher.md | 56 +- docs/string/match.md | 6 +- docs/string/prefix-function.md | 88 +- docs/string/sa.md | 24 +- docs/string/sam.md | 223 ++- docs/string/trie.md | 4 +- docs/string/z-function.md | 104 +- package-lock.json | 2594 ++++++++++++++-------------- 156 files changed, 4010 insertions(+), 4065 deletions(-) diff --git a/docs/basic/binary.md b/docs/basic/binary.md index d16704b3..c684aae0 100644 --- a/docs/basic/binary.md +++ b/docs/basic/binary.md @@ -33,7 +33,7 @@ int binary_search(int start, int end, int key) { `>> 1` 比 `/ 2` 速度快一些 -注意,这里的有序是广义的有序,如果一个数组中的左侧或者右侧都满足某一种条件,而另一侧都不满足这种条件,也可以看作是一种有序(如果把满足条件看做 $1$,不满足看做 $0$,至少对于这个条件的这一维度是有序的)。换言之,二分搜索法可以用来查找满足某种条件的最大(最小)的值。 +注意,这里的有序是广义的有序,如果一个数组中的左侧或者右侧都满足某一种条件,而另一侧都不满足这种条件,也可以看作是一种有序(如果把满足条件看做 $1$ ,不满足看做 $0$ ,至少对于这个条件的这一维度是有序的)。换言之,二分搜索法可以用来查找满足某种条件的最大(最小)的值。 如果我们要求满足某种条件的最大值的最小可能情况(最大值最小化)呢?首先的想法是从小到大枚举这个作为答案的「最大值」,然后去判断是否合法。要是这个答案是单调的就好了,那样就可以使用二分搜索法来更快地找到答案。 @@ -49,15 +49,15 @@ int binary_search(int start, int end, int key) { ### STL 的二分查找 -补充一个小知识点, 对于一个有序的 array 你可以使用 `std::lower_bound()` 来找到第一个大于等于你的值的数, `std::upper_bound()` 来找到第一个大于你的值的数。 +补充一个小知识点,对于一个有序的 array 你可以使用 `std::lower_bound()` 来找到第一个大于等于你的值的数, `std::upper_bound()` 来找到第一个大于你的值的数。 请注意,必须是有序数组,否则答案是错误的。 -关于具体使用方法,请参见 [STL 页面](/ds/stl/)。 +关于具体使用方法,请参见[STL 页面](/ds/stl/)。 ### 二分答案 -解题的时候往往会考虑枚举答案然后检验枚举的值是否正确。如果我们把这里的枚举换成二分,就变成了 “二分答案”。 +解题的时候往往会考虑枚举答案然后检验枚举的值是否正确。如果我们把这里的枚举换成二分,就变成了“二分答案”。 来看一看一道例题[Luogu P1873 砍树](https://www.luogu.org/problemnew/show/P1873),我们可以从 1 到 1000000000(10 亿)来枚举答案,但是这种朴素写法肯定拿不到满分,因为从 1 跑到 10 亿太耗时间。我们可以对答案进行 1 到 10 亿的二分,其中,每次都对其进行检查可行性(一般都是使用贪心法)。**这就是二分答案。** @@ -134,7 +134,7 @@ else ## 分数规划 -分数规划是这样一类问题,每个物品有两个代价 $c_i$,$d_i$,要求通过某种方式选出若干个,使得 $\frac{\sum{c_i}}{\sum{d_i}}$ 最大或最小。 +分数规划是这样一类问题,每个物品有两个代价 $c_i$ , $d_i$ ,要求通过某种方式选出若干个,使得 $\frac{\sum{c_i}}{\sum{d_i}}$ 最大或最小。 经典的例子是 最优比率环、最优比率生成树 等等。 @@ -146,7 +146,7 @@ $$ L \geq \frac{\sum{c_i}}{\sum{d_i}} $$ -把分母乘过去,把右侧化为 $0$: +把分母乘过去,把右侧化为 $0$ : $$ {\sum{d_i}} \times L - {\sum{c_i}} \geq 0 @@ -164,7 +164,7 @@ $$ 不难发现,如果 $L'$ 比 $L$ 要小,上式左端的值会更大一些。 -所以要求得最小的 $L$,我们要求的就变成了让上式左端最接近 $0$ 的 $L$。 +所以要求得最小的 $L$ ,我们要求的就变成了让上式左端最接近 $0$ 的 $L$ 。 不难发现左端的式子是随 $L$ 变化而单调变化的,所以可以通过二分法来解决。 diff --git a/docs/basic/divide-and-conquer.md b/docs/basic/divide-and-conquer.md index 2f2b8cd6..e0d43e0b 100644 --- a/docs/basic/divide-and-conquer.md +++ b/docs/basic/divide-and-conquer.md @@ -2,7 +2,7 @@ 介绍分治之前,首先要弄清楚递归这个概念。 -递归是什么呢?举个简单的例子:从前有座山,山上有座庙,庙里有个老和尚,老和尚给小和尚讲故事:“从前有座山,山上有座庙,庙里有个老和尚,老和尚给小和尚讲故事:‘从前有座山......’” 这个故事与递归算法有着异曲同工之妙。 +递归是什么呢?举个简单的例子:从前有座山,山上有座庙,庙里有个老和尚,老和尚给小和尚讲故事:“从前有座山,山上有座庙,庙里有个老和尚,老和尚给小和尚讲故事:‘从前有座山……’”这个故事与递归算法有着异曲同工之妙。 递归的基本思想是某个函数直接或者间接地调用自身,这样就把原问题的求解转换为许多性质相同但是规模更小的子问题。我们只需要关注如何把原问题划分成符合条件的子问题,而不需要去研究这个子问题是如何被解决的。 @@ -41,7 +41,7 @@ int f(传入数值) { 这道题朴素的递归写法只能得到 25 分,因为递归次数太多,所以超时。 -怎么优化呢?详见 [搜索优化](/search/optimization) 和 [记忆化搜索](/dp/memo/)。 +怎么优化呢?详见[搜索优化](/search/optimization)和[记忆化搜索](/dp/memo/)。 ## 分治 diff --git a/docs/basic/expression.md b/docs/basic/expression.md index 69e069fe..e0c0e04b 100644 --- a/docs/basic/expression.md +++ b/docs/basic/expression.md @@ -6,10 +6,9 @@ ## 递归 -递归的方法是把表达式拆分成如图所示的表达式树,然后在树结构上自底向上进行运算。 -![](./images/bet.png) +递归的方法是把表达式拆分成如图所示的表达式树,然后在树结构上自底向上进行运算。![](./images/bet.png) -表达式树上进行 [树的遍历](/graph/traverse/#dfs_3) 可以得到不同类型的表达式 +表达式树上进行[树的遍历](/graph/traverse/#dfs_3)可以得到不同类型的表达式 - 前序遍历对应前缀表达式(波兰式) - 中序遍历对应中缀表达式 @@ -17,9 +16,9 @@ ## 非递归 -非递归的方法是定义两个 [栈](/stack/) 来分别存储运算符和运算数。每当遇到一个数直接放进数的栈;每当遇到一个操作符时,要查找之前运算符栈中的元素,按照预先定义好的优先级来进行适当的弹出操作(弹出的同时求出对应的子表达式的值)。 +非递归的方法是定义两个[栈](/stack/)来分别存储运算符和运算数。每当遇到一个数直接放进数的栈;每当遇到一个操作符时,要查找之前运算符栈中的元素,按照预先定义好的优先级来进行适当的弹出操作(弹出的同时求出对应的子表达式的值)。 -我们要知道:算术表达式分为三种,分别是前缀表达式、中缀表达式、后缀表达式。其中,中缀表达式是我们日常生活中最常用的表达式;后缀表达式是计算机最容易理解的表达式。为什么说后缀表达式最容易被计算机理解呢?因为后缀表达式不需要括号表示,它的运算顺序是唯一确定的。举个例子:在后缀表达式 $3 2 * 1 -$ 中,首先计算 $3 \times 2 = 6$(使用最后一个运算符,即栈顶运算符),然后计算 $6 - 1 = 5$。可以看到:对于一个后缀表达式,只需要 ** 维护一个数字栈,每次遇到一个运算符,就取出两个栈顶元素,将运算结果重新压入栈中 **。最后,栈中唯一一个元素就是改后缀表达式的运算结果时间复杂度 $O(n)$。 +我们要知道:算术表达式分为三种,分别是前缀表达式、中缀表达式、后缀表达式。其中,中缀表达式是我们日常生活中最常用的表达式;后缀表达式是计算机最容易理解的表达式。为什么说后缀表达式最容易被计算机理解呢?因为后缀表达式不需要括号表示,它的运算顺序是唯一确定的。举个例子:在后缀表达式 $3 2 * 1 -$ 中,首先计算 $3 \times 2 = 6$ (使用最后一个运算符,即栈顶运算符),然后计算 $6 - 1 = 5$ 。可以看到:对于一个后缀表达式,只需要**维护一个数字栈,每次遇到一个运算符,就取出两个栈顶元素,将运算结果重新压入栈中**。最后,栈中唯一一个元素就是改后缀表达式的运算结果时间复杂度 $O(n)$ 。 所以说,对于普通中缀表达式的计算,我们可以将其转化为后缀表达式再进行计算。转换方法也十分简单。只要建立一个用于存放运算符的栈,扫描该中缀表达式: @@ -29,7 +28,7 @@ 4. 如果遇到其他运算符,不断去除所有运算优先级大于等于当前运算符的运算符,输出。最后,新的符号入栈。 5. 把栈中剩下的符号依次输出,表达式转换结束。 -时间复杂度 $O(n)$. +时间复杂度 $O(n)$ . 示例代码: @@ -87,6 +86,6 @@ int calc(const std::string &s) { // 计算转换好的后缀表达式 ## 习题 -1. [表达式求值 (NOIP2013)](https://www.luogu.org/problemnew/show/P1981) +1. [表达式求值(NOIP2013)](https://www.luogu.org/problemnew/show/P1981) 2. [后缀表达式](https://www.luogu.org/problemnew/show/P1449) 3. [Transform the Expression](https://www.spoj.com/problems/ONP/) diff --git a/docs/basic/file-operation.md b/docs/basic/file-operation.md index a8f1165e..71abf809 100644 --- a/docs/basic/file-operation.md +++ b/docs/basic/file-operation.md @@ -5,11 +5,11 @@ C/C++ 将文件分为文本文件和二进制文件。文本文件就是简单 ## 文件的操作步骤 - 1、打开文件,将文件指针指向文件,决定打开文件类型; - 2、对文件进行读、写操作(比赛中主要用到的操作,其他一些操作暂时不写); - 3、在使用完文件后,关闭文件。 +1、打开文件,将文件指针指向文件,决定打开文件类型; +2、对文件进行读、写操作(比赛中主要用到的操作,其他一些操作暂时不写); +3、在使用完文件后,关闭文件。 -## `freopen` 函数 +## `freopen` 函数 ### 命令格式 @@ -19,9 +19,9 @@ FILE* freopen(const char* filename, const char* mode, FILE* stream); ### 参数说明 -- `filename`: 要打开的文件名 -- `mode`: 文件打开的模式 -- `stream`: 文件指针,通常使用标准文件流 (`stdin/stdout/stderr`) +- `filename` : 要打开的文件名 +- `mode` : 文件打开的模式 +- `stream` : 文件指针,通常使用标准文件流 ( `stdin/stdout/stderr` ) ### 使用方法 @@ -39,7 +39,7 @@ freopen("data.out", "w", stdout); // data.out 就是输出文件的文件名,和可执行文件在同一目录下 ``` -关闭标准输入 / 输出流 +关闭标准输入/输出流 ```cpp fclose(stdin); @@ -83,7 +83,7 @@ ofstream fout("data.out"); // data.out 就是输出文件的文件名,和可执行文件在同一目录下 ``` -关闭标准输入 \\ 输出流 +关闭标准输入\\输出流 ```cpp fin.close(); diff --git a/docs/basic/greedy.md b/docs/basic/greedy.md index 5cfb38bc..c3d0de07 100644 --- a/docs/basic/greedy.md +++ b/docs/basic/greedy.md @@ -1,12 +1,12 @@ -贪心算法顾名思义就是用计算机来模拟一个 “贪心” 的人做出决策的过程。 +贪心算法顾名思义就是用计算机来模拟一个“贪心”的人做出决策的过程。 -这个人每一步行动总是按某种指标选取最优的操作,他总是 ** 只看眼前,并不考虑以后可能造成的影响 ** 。 +这个人每一步行动总是按某种指标选取最优的操作,他总是**只看眼前,并不考虑以后可能造成的影响**。 可想而知,并不是所有的时候贪心法都能获得最优解,所以一般使用贪心法的时候,都要确保自己能证明其正确性。 ## 常见做法 -在提高组难度以下的题目中,最常见的贪心有两种。一种是:「我们将 XXX 按照某某顺序排序,然后按某种顺序(例如从小到大)处理」。另一种是:「我们每次都取 XXX 中最大 / 小的东西,并更新 XXX」,有时「XXX 中最大 / 小的东西」可以优化,比如用优先队列维护。 +在提高组难度以下的题目中,最常见的贪心有两种。一种是:「我们将 XXX 按照某某顺序排序,然后按某种顺序(例如从小到大)处理」。另一种是:「我们每次都取 XXX 中最大/小的东西,并更新 XXX」,有时「XXX 中最大/小的东西」可以优化,比如用优先队列维护。 为啥分成两种?你可以发现,一种是离线的,一种是在线的。 @@ -16,16 +16,16 @@ 以下套路请按照题目自行斟酌,一般情况下一道题只会用到其中的一种方法来证明。 -1. 运用反证法,如果交换方案中任意两个元素 / 相邻的两个元素后,答案不会变得更好,那么可以发现目前的解已经是最优解了。 -2. 运用归纳法,先手算得出边界情况(例如 $n = 1$ )的最优解 $F_1$ ,然后再证明:对于每个 $n$ ,$F_{n+1}$ 都可以由 $F_{n}$ 推导出结果。 +1. 运用反证法,如果交换方案中任意两个元素/相邻的两个元素后,答案不会变得更好,那么可以发现目前的解已经是最优解了。 +2. 运用归纳法,先手算得出边界情况(例如 $n = 1$ )的最优解 $F_1$ ,然后再证明:对于每个 $n$ , $F_{n+1}$ 都可以由 $F_{n}$ 推导出结果。 ## [排序法](https://goldimax.github.io/atricle.html?5b82a0a49f54540031c06bd8) 用排序法常见的情况是输入一个包含几个(一般一到两个)权值的数组,通过排序然后遍历模拟计算的方法求出最优值。 -有些题的排序方法非常显然,如 [ luogu P1209 ](https://www.luogu.org/problemnew/show/P1209) 就是将输入数组差分后排序模拟求值。 +有些题的排序方法非常显然,如[luogu P1209](https://www.luogu.org/problemnew/show/P1209)就是将输入数组差分后排序模拟求值。 -然而有些时候很难直接一下子看出排序方法,比如 [ luogu P1080 ](https://www.luogu.org/problemnew/show/P1080) 就很容易凭直觉而错误地以 $a$ 或 $b$ 为关键字排序,过样例之后提交就发现 WA 了 QAQ。一个 ~~众所周知的~~ 常见办法就是尝试交换数组相邻的两个元素来**推导**出正确的排序方法。我们假设这题输入的俩个数用一个结构体来保存 +然而有些时候很难直接一下子看出排序方法,比如[luogu P1080](https://www.luogu.org/problemnew/show/P1080)就很容易凭直觉而错误地以 $a$ 或 $b$ 为关键字排序,过样例之后提交就发现 WA 了 QAQ。一个~~众所周知的~~常见办法就是尝试交换数组相邻的两个元素来**推导**出正确的排序方法。我们假设这题输入的俩个数用一个结构体来保存 ```c++ struct { @@ -33,43 +33,43 @@ struct { } v[n]; ``` -用 $m$ 表示 $i$ 前面所有的 $a$ 的乘积,那么第 $i$ 个大臣得到的奖赏就是 +用 $m$ 表示 $i$ 前面所有的 $a$ 的乘积,那么第 $i$ 个大臣得到的奖赏就是 $$ \frac{m} {v[i].b} $$ -第 $i + 1$ 个大臣得到的奖赏就是 +第 $i + 1$ 个大臣得到的奖赏就是 $$ \frac{m \cdot v[i].a} {v[i + 1].b} $$ -如果我们交换第 $i$ 个大臣与第 $i + 1$ 个大臣的位置,那么第 $i + 1$ 个大臣得到的奖赏就是 +如果我们交换第 $i$ 个大臣与第 $i + 1$ 个大臣的位置,那么第 $i + 1$ 个大臣得到的奖赏就是 $$ \frac{m} {v[i + 1].b} $$ -第 $i + 1$ 个大臣得到的奖励就是 +第 $i + 1$ 个大臣得到的奖励就是 $$ \frac{m \cdot v[i + 1].a} {v[i].b} $$ -如果交前更优当且仅当 +如果交前更优当且仅当 $$ \max (\frac{m} {v[i].b}, \frac{m \times v[i].a} {v[i + 1].b}) < \max (\frac{m} {v[i + 1].b}, \frac{m \times v[i + 1].a} {v[i].b}) $$ -提取出相同的 $m$ 并约分得到 +提取出相同的 $m$ 并约分得到 $$ \max(\frac{1} {v[i].b}, \frac{v[i].a} {v[i + 1].b}) < \max(\frac{1} {v[i + 1].b}, \frac{v[i + 1].a} {v[i].b}) $$ -然后分式化成整式得到 +然后分式化成整式得到 $$ \max(v[i + 1].b, v[i].a \times v[i].b) < \max(v[i].b, v[i + 1].a \times v[i + 1].b) @@ -86,18 +86,18 @@ struct uv { }; ``` -~~看上去是不是很简单呢(这题高精度卡常……)~~ ,如果看懂了就可以尝试下一道类似的题 [luogu P2123](https://www.luogu.org/problemnew/show/P2123)(请不要翻题解……。 +~~看上去是不是很简单呢(这题高精度卡常……)~~,如果看懂了就可以尝试下一道类似的题[luogu P2123](https://www.luogu.org/problemnew/show/P2123)(请不要翻题解……。 ## 后悔法 -??? note " 例题 [luogu P2949 \[USACO09OPEN\] 工作调度 Work Scheduling ](https://www.luogu.org/problemnew/show/P2949)" +??? note " 例题[luogu P2949\[USACO09OPEN\]工作调度 Work Scheduling](https://www.luogu.org/problemnew/show/P2949)" 贪心思想: -- **1** . 先假设每一项工作都做,将各项工作按截止时间排序后入队。 -- **2** . 在判断第 i 项工作做与不做时,若其截至时间符合条件,则将其与队中报酬最小的元素比较,若第 i 项工作报酬较高(后悔),则 ans+=a[i].p-q.top()。 +- **1**. 先假设每一项工作都做,将各项工作按截止时间排序后入队。 +- **2**. 在判断第 i 项工作做与不做时,若其截至时间符合条件,则将其与队中报酬最小的元素比较,若第 i 项工作报酬较高(后悔),则 ans+=a[i].p-q.top()。 -**PS** : 用优先队列(小根堆)来维护队首元素最小。 +**PS**:用优先队列(小根堆)来维护队首元素最小。 ### Code diff --git a/docs/basic/prefix-sum.md b/docs/basic/prefix-sum.md index 3e9881ba..c266cb41 100644 --- a/docs/basic/prefix-sum.md +++ b/docs/basic/prefix-sum.md @@ -1,14 +1,14 @@ ## 前缀和 -前缀和是一种较为简单的算法,可以大大减少时间复杂度。我们可以简单理解为 “数列的前 n 项的和”。下面我们用一个例题来了解一下前缀和的主要思路。 +前缀和是一种较为简单的算法,可以大大减少时间复杂度。我们可以简单理解为“数列的前 n 项的和”。下面我们用一个例题来了解一下前缀和的主要思路。 !!! 例题 - 有 N 个的正整数放到数组 A 里,现在要求一个新的数组 B,新数组的第 i 个数 B[i] 是原数组 A 第 0 到第 i 个数的和。 + 有 N 个的正整数放到数组 A 里,现在要求一个新的数组 B,新数组的第 i 个数 B[i]是原数组 A 第 0 到第 i 个数的和。 对于这道题,我们有两种做法: - 把对数组 A 的累加依次放入数组 B 中。 -- 递推:`B[i] = B[i-1] + A[i]` ,前提 `B[0] = A[0]`。 +- 递推: `B[i] = B[i-1] + A[i]` ,前提 `B[0] = A[0]` 。 参考程序: @@ -47,9 +47,9 @@ int main() { 1 3 6 10 15 -首先,`B[0] = A[0];`,前缀和数组的第一项和原数组的第一项是相等的。 +首先, `B[0] = A[0];` ,前缀和数组的第一项和原数组的第一项是相等的。 -`B[i] = B[i-1] + A[i]` 相信大家也能理解这个式子。意思就是:前缀和数组的第 i 项等于数组 A 的 0 到 i-1 项的和加数组 A 的第 i 项。 + `B[i] = B[i-1] + A[i]` 相信大家也能理解这个式子。意思就是:前缀和数组的第 i 项等于数组 A 的 0 到 i-1 项的和加数组 A 的第 i 项。 ### 习题 @@ -59,12 +59,12 @@ int main() { 感谢南海区青少年信息学奥林匹克内部训练教材。 -## 二维 / 多维前缀和 +## 二维/多维前缀和 其实前缀和几乎都是基于容斥原理,所以各种拓展自己手推一下就行了。 这里用二维前缀和为例讲解一下前缀和扩展到多维的方式。 -比如我们有这样一个矩阵 $a$,可以视为二维数组: +比如我们有这样一个矩阵 $a$ ,可以视为二维数组: ```plain 1 2 4 3 @@ -72,7 +72,7 @@ int main() { 6 3 5 9 ``` -我们定义一个矩阵 $sum$,$sum_{x,y} = \sum\limits_{i=1}^x \sum\limits_{j=1}^y a_{i,j}$, +我们定义一个矩阵 $sum$ , $sum_{x,y} = \sum\limits_{i=1}^x \sum\limits_{j=1}^y a_{i,j}$ , 那么这个矩阵长这样: ```plain @@ -81,11 +81,11 @@ int main() { 12 18 29 45 ``` -第一个问题就是递推求 $sum$ 的过程,$sum_{i,j} = sum_{i - 1,j} + sum_{i,j - 1} - sum_{i - 1,j - 1} + a_{i,j}$。 -因为加了 $sum_{i - 1,j}$ 和 $sum_{i,j - 1}$ 重复了 $sum_{i - 1,j - 1}$,所以减去。 +第一个问题就是递推求 $sum$ 的过程, $sum_{i,j} = sum_{i - 1,j} + sum_{i,j - 1} - sum_{i - 1,j - 1} + a_{i,j}$ 。 +因为加了 $sum_{i - 1,j}$ 和 $sum_{i,j - 1}$ 重复了 $sum_{i - 1,j - 1}$ ,所以减去。 第二个问题就是如何应用,譬如求 $(x1,y1) - (x2,y2)$ 子矩阵的和。 -那么,根据类似的思考过程,易得答案为 $sum_{x2,y2} - sum_{x1 - 1,y2} - sum_{x2,y1 - 1} + sum_{x1 - 1,y1 - 1}$。 +那么,根据类似的思考过程,易得答案为 $sum_{x2,y2} - sum_{x1 - 1,y2} - sum_{x2,y1 - 1} + sum_{x1 - 1,y1 - 1}$ 。 ### 习题 @@ -94,8 +94,8 @@ int main() { ## 树上前缀和 设 $sum_i$ 表示结点 $i$ 到根节点的权值总和。 -然后若是点权,$x,y$ 路径上的和为 $sum_x + sum_y - sum_{lca} - sum_{fa_{lca}}$; -否则若是边权,$x,y$ 路径上的和为 $sum_x + sum_y - 2sum_{lca}$。 +然后若是点权, $x,y$ 路径上的和为 $sum_x + sum_y - sum_{lca} - sum_{fa_{lca}}$ ; +否则若是边权, $x,y$ 路径上的和为 $sum_x + sum_y - 2sum_{lca}$ 。 至于 lca 的求法请移步[最近公共祖先](/graph/lca/)。 @@ -107,11 +107,11 @@ int main() { ## 差分 差分,是一种和前缀和相对的策略。 -这种策略是,令 $b_i = a_i - a_{i - 1}$,即相邻两数的差。 +这种策略是,令 $b_i = a_i - a_{i - 1}$ ,即相邻两数的差。 易得对这个序列做一遍前缀和就得到了原来的 $a$ 序列。 它可以维护多次对序列的一个区间加上一个数,并在最后询问某一位的数或是多次询问某一位的数。(总之修改操作一定要在查询操作之前) -具体怎么搞?譬如使 $[l,r]$ 每个数加上一个 $k$,就是 $b_l \leftarrow b_l + k,b_{r + 1} \leftarrow b_{r + 1} - k$。 +具体怎么搞?譬如使 $[l,r]$ 每个数加上一个 $k$ ,就是 $b_l \leftarrow b_l + k,b_{r + 1} \leftarrow b_{r + 1} - k$ 。 最后做一遍前缀和就好了。 对于多维差分,自己手推一下就好了。(逃 @@ -128,8 +128,8 @@ int main() { 至少人家是基于子树和而非到根的和。 -如果使 $x,y$ 路径上的点权增加 $k$,$b_x \leftarrow b_x + k,b_y \leftarrow b_y + k,b_{lca} \leftarrow b_{lca} - k,b_{fa_{lca}} \leftarrow b_{fa_{lca}} - k$, -如果是边权,$b_x \leftarrow b_x + k,b_y \leftarrow b_y + k,b_{lca} \leftarrow b_{lca} - 2k$。 +如果使 $x,y$ 路径上的点权增加 $k$ , $b_x \leftarrow b_x + k,b_y \leftarrow b_y + k,b_{lca} \leftarrow b_{lca} - k,b_{fa_{lca}} \leftarrow b_{fa_{lca}} - k$ , +如果是边权, $b_x \leftarrow b_x + k,b_y \leftarrow b_y + k,b_{lca} \leftarrow b_{lca} - 2k$ 。 然后一遍搜索求一下子树和答案就出来了。 diff --git a/docs/basic/simulate.md b/docs/basic/simulate.md index 5dd17391..860829b4 100644 --- a/docs/basic/simulate.md +++ b/docs/basic/simulate.md @@ -1,6 +1,6 @@ -模拟。顾名思义,就是用计算机来模拟题目中要求的操作,比如 NOIP 2014 的 [生活大爆炸版石头剪刀布](https://loj.ac/problem/2498),只需要按照题面的意思来写就可以了。 +模拟。顾名思义,就是用计算机来模拟题目中要求的操作,比如 NOIP 2014 的[生活大爆炸版石头剪刀布](https://loj.ac/problem/2498),只需要按照题面的意思来写就可以了。 -当然,模拟一般也不是很好写,参见经典题目 [魔兽世界](http://bailian.openjudge.cn/practice/3750/) 和 [猪国杀](https://www.lydsy.com/JudgeOnline/problem.php?id=1972)。 +当然,模拟一般也不是很好写,参见经典题目[魔兽世界](http://bailian.openjudge.cn/practice/3750/)和[猪国杀](https://www.lydsy.com/JudgeOnline/problem.php?id=1972)。 模拟题目通常具有码量大、操作多、思路繁复的特点。并且由于它码量大,会导致很难查错,如果是在考试上是相当浪费时间的。 @@ -8,6 +8,6 @@ 1. 在动手写代码之前,在草纸上尽可能的写好要实现的流程 2. 在代码中,尽量把每个部分模块化、写成函数、结构体或类 -3. 对于一些可能重复用到的概念,可以统一转化,方便处理 : 如,某题给你 "YY-MM-DD 时: 分" 把它扔到一个函数处理成秒,会减少概念混淆 +3. 对于一些可能重复用到的概念,可以统一转化,方便处理:如,某题给你 "YY-MM-DD 时:分" 把它扔到一个函数处理成秒,会减少概念混淆 4. 调试时分块调试,模块化的好处就是可以方便的单独调某一部分 5. 写代码的时候一定要思路清晰,不要想到什么写什么,要按照落在纸上的步骤写。 diff --git a/docs/basic/sort.md b/docs/basic/sort.md index 7384b81c..b18564de 100644 --- a/docs/basic/sort.md +++ b/docs/basic/sort.md @@ -10,13 +10,13 @@ 时间复杂度用来衡量一个算法的运行时间和输入规模的关系,类似的有空间复杂度,用来描述算法的空间消耗的规模。 -简单计算复杂度的方法一般是统计 “简单操作” 的执行次数,有时候也可以直接数循环的层数来近似估计。 +简单计算复杂度的方法一般是统计“简单操作”的执行次数,有时候也可以直接数循环的层数来近似估计。 时间复杂度分为最坏时间复杂度、平均时间复杂度、最好时间复杂度等等。OI 竞赛中要考虑的一般是最坏时间复杂度,因为它代表的是算法运行水平的下界,在评测中不会出现更差的结果了。 基于比较的排序算法的时间复杂度下限是 $O(n\log n)$ 的。 -当然也有不是 $O(n\log n)$ 的,桶排序的时间复杂度是 $O(n)$,但是它是在「用空间换时间」,它的空间复杂度是 $O($所排序的最大数$)$ +当然也有不是 $O(n\log n)$ 的,桶排序的时间复杂度是 $O(n)$ ,但是它是在「用空间换时间」,它的空间复杂度是 $O($ 所排序的最大数 $)$ ## 冒泡排序 @@ -42,11 +42,11 @@ void bubble_sort() { } ``` -在序列完全有序时,该算法只需遍历一遍数组,不用执行任何交换操作,时间复杂度为 $O(n)$。在最坏情况下,冒泡排序要执行 $(n-1) \times n/2$ 次交换操作,时间复杂度为 $O(n^2)$。在平均情况下,冒泡排序的时间复杂度也是 $O(n^2)$。 +在序列完全有序时,该算法只需遍历一遍数组,不用执行任何交换操作,时间复杂度为 $O(n)$ 。在最坏情况下,冒泡排序要执行 $(n-1) \times n/2$ 次交换操作,时间复杂度为 $O(n^2)$ 。在平均情况下,冒泡排序的时间复杂度也是 $O(n^2)$ 。 ## 归并排序 -归并排序是 [分治](/basic/divide-and-conquer) 地来将一个数组排序。 +归并排序是[分治](/basic/divide-and-conquer)地来将一个数组排序。 归并排序分为三个过程: @@ -56,7 +56,7 @@ void bubble_sort() { 不难发现,归并排序的核心是如何合并两个子序列,前两步都很好实现。 -其实合并的时候也不难操作。注意到两个子序列在第二步中已经保证了都是有序的了,第三步中实际上是想要把两个 **有序** 数列合并起来。 +其实合并的时候也不难操作。注意到两个子序列在第二步中已经保证了都是有序的了,第三步中实际上是想要把两个**有序**数列合并起来。 ```c++ void merge(int ll, int rr) { @@ -77,21 +77,21 @@ void merge(int ll, int rr) { } ``` -由于 `||` 是短路运算符,这里面 if 判断的情况是 “第一部分已经完全合并完了” 或者 “两个都没有合并完,且前一个的队首要大于后一个”,这两种情况都是要把后一个子序列的队首放到新序列的当前位置中。 +由于 `||` 是短路运算符,这里面 if 判断的情况是“第一部分已经完全合并完了”或者“两个都没有合并完,且前一个的队首要大于后一个”,这两种情况都是要把后一个子序列的队首放到新序列的当前位置中。 ### 逆序对 归并排序还可以用来求逆序对的个数。 -所谓逆序对,就是数对 $(i, j)$,满足 $a[i] > a[j]$ 且 $i < j$。 +所谓逆序对,就是数对 $(i, j)$ ,满足 $a[i] > a[j]$ 且 $i < j$ 。 -可以用 [树状数组](/ds/bit)、[线段树](/ds/segment/) 等数据结构来求,也可以用归并排序来求。 +可以用[树状数组](/ds/bit)、[线段树](/ds/segment/)等数据结构来求,也可以用归并排序来求。 具体来说,上面归并排序中间注释掉的 `ans += md - p` 就是在统计逆序对个数。 -是因为,那里把靠后的数放到前面了(较小的数放在前面),所以这个数的原来位置以前的、比它大的数都会和他形成逆序对,而这个个数就是还没有合并进去的数的个数,即为 `md - p`。 +是因为,那里把靠后的数放到前面了(较小的数放在前面),所以这个数的原来位置以前的、比它大的数都会和他形成逆序对,而这个个数就是还没有合并进去的数的个数,即为 `md - p` 。 -使用归并排序求逆序对的时间复杂度也是$O(n \log n)$。 +使用归并排序求逆序对的时间复杂度也是 $O(n \log n)$ 。 ### 参考 @@ -99,7 +99,7 @@ void merge(int ll, int rr) { ## 快速排序 -快速排序是 [分治](/basic/divide-and-conquer) 地来将一个数组排序。 +快速排序是[分治](/basic/divide-and-conquer)地来将一个数组排序。 快速排序分为三个过程: @@ -121,21 +121,21 @@ void merge(int ll, int rr) { 其实,快速排序没有指定应如何具体实现第一步,不论是选择 m 的过程还是划分的过程,都不是只有一种实现方法。 -注意,一般我们说的快速排序的时间复杂度是平均为 $O(N\log N)$,最坏是 $O(n^2)$,只不过实践中几乎不可能达到最坏情况。 +注意,一般我们说的快速排序的时间复杂度是平均为 $O(N\log N)$ ,最坏是 $O(n^2)$ ,只不过实践中几乎不可能达到最坏情况。 -其实,在选择 m 的过程中使用 [Median of Medians](https://en.wikipedia.org/wiki/Median_of_medians) 算法,就可以保证最坏时间复杂度为 $O(N\log N)$,但是由于 j 小微复杂,实践中一般不使用。 +其实,在选择 m 的过程中使用[Median of Medians](https://en.wikipedia.org/wiki/Median_of_medians)算法,就可以保证最坏时间复杂度为 $O(N\log N)$ ,但是由于 j 小微复杂,实践中一般不使用。 ### STL -C 函数模板库实现了快速排序,即 `stdlib.h` 当中的 `qsort`。 +C 函数模板库实现了快速排序,即 `stdlib.h` 当中的 `qsort` 。 但在 OI 相关比赛当中,更为常见的库排序函数是 C++ `algorithm` 库中的 `std::sort` 函数。 C++ 标准并未严格要求此函数的实现算法,具体实现取决于编译器。 -旧版 C++ 标准中仅要求它的 **平均** 时间复杂度是 $O(N\log N)$ 的,但在 C++11 中要求它的 **最坏** 时间复杂度是 $O(N\log N)$ 的。可以查阅 [std::sort()](https://en.cppreference.com/w/cpp/algorithm/sort) +旧版 C++ 标准中仅要求它的**平均**时间复杂度是 $O(N\log N)$ 的,但在 C++11 中要求它的**最坏**时间复杂度是 $O(N\log N)$ 的。可以查阅[std::sort()](https://en.cppreference.com/w/cpp/algorithm/sort) -在 [libstdc++](https://github.com/mirrors/gcc/blob/master/libstdc++-v3/include/bits/stl_algo.h) 和 [libc++](http://llvm.org/svn/llvm-project/libcxx/trunk/include/algorithm) 中使用的都是 [Introsort](https://en.wikipedia.org/wiki/Introsort)。 +在[libstdc++](https://github.com/mirrors/gcc/blob/master/libstdc++-v3/include/bits/stl_algo.h)和[libc++](http://llvm.org/svn/llvm-project/libcxx/trunk/include/algorithm)中使用的都是[Introsort](https://en.wikipedia.org/wiki/Introsort)。 Introsort 限制了快速排序的分治深度,当分治达到一定深度之后,改用最坏时间复杂度为 $O(N\log N)$ 的排序算法(比如堆排序)来给子数组排序。 @@ -151,11 +151,11 @@ std::sort(a, a + n); ### 线性找第 k 大的数 -找第 k 大的数,最简单的方法是先排序,然后直接找到第 k 大的位置的元素。这样做的时间复杂度是 $O(N\log N)$,对于这个问题来说很不划算。事实上,我们有 $O(n)$ 的解法。 +找第 k 大的数,最简单的方法是先排序,然后直接找到第 k 大的位置的元素。这样做的时间复杂度是 $O(N\log N)$ ,对于这个问题来说很不划算。事实上,我们有 $O(n)$ 的解法。 -考虑快速排序的划分过程,在快速排序的 “划分” 结束后,数列 $A[p \cdots r]]$ 被分成了 $A[p \cdots q]$ 和 $A[q+1 \cdots r]$,此时可以按照左边元素的个数($q - p + 1$)和 k 的大小关系来判断是只在左边还是只在右边递归地求解。 +考虑快速排序的划分过程,在快速排序的“划分”结束后,数列 $A[p \cdots r]]$ 被分成了 $A[p \cdots q]$ 和 $A[q+1 \cdots r]$ ,此时可以按照左边元素的个数( $q - p + 1$ )和 k 的大小关系来判断是只在左边还是只在右边递归地求解。 -可以证明,在期望意义下,程序的时间复杂度为 $O(n)$。 +可以证明,在期望意义下,程序的时间复杂度为 $O(n)$ 。 ### 参考 @@ -168,16 +168,16 @@ std::sort(a, a + n); 计数排序可以在 $O(n)$ 的时间内排序,但是它要求所有的数都出现在一定的范围内。 !!! warning "注" - 注意区分 ** 基数排序 ** + 注意区分**基数排序** 算法流程顾名思义,就是记录每一个数出现了多少次,然后从小到大依次输出。 -一般考虑的是某一范围内的整数,但是计数排序也可以和 [离散化](/misc/discrete) 一起使用,来对浮点数、大整数进行计数排序。 +一般考虑的是某一范围内的整数,但是计数排序也可以和[离散化](/misc/discrete)一起使用,来对浮点数、大整数进行计数排序。 ### 参考 - ATool 的排序演示动画 - +ATool 的排序演示动画 + ## 排序的应用 @@ -185,8 +185,8 @@ std::sort(a, a + n); 考虑一个数列,你需要检查其中是否有元素相等。 -一个朴素的做法是检查每一个数对,并判断这一对数是否相等。时间复杂度是 $O(n^2)$。 +一个朴素的做法是检查每一个数对,并判断这一对数是否相等。时间复杂度是 $O(n^2)$ 。 -我们不妨先对这一列数排序,之后不难发现:如果有相等的两个数,它们一定在新数列中处于相邻的位置上。这时,只需要 $O(n)$ 地扫一遍新数列了。总的时间复杂度是排序的复杂度($O(n\log n)$)。 +我们不妨先对这一列数排序,之后不难发现:如果有相等的两个数,它们一定在新数列中处于相邻的位置上。这时,只需要 $O(n)$ 地扫一遍新数列了。总的时间复杂度是排序的复杂度( $O(n\log n)$ )。 -排序也是 [二分查找](/basic/binary/) 所要做的预处理工作。在排序后使用 [二分查找](/basic/binary/),我们可以在 $O(\log n)$ 的时间内在序列中查找指定的元素。 +排序也是[二分查找](/basic/binary/)所要做的预处理工作。在排序后使用[二分查找](/basic/binary/),我们可以在 $O(\log n)$ 的时间内在序列中查找指定的元素。 diff --git a/docs/dp/backpack.md b/docs/dp/backpack.md index 6f802812..9d8919ef 100644 --- a/docs/dp/backpack.md +++ b/docs/dp/backpack.md @@ -2,16 +2,16 @@ 在具体讲何为 "背包 dp" 前,先来看如下的例题 -??? note " 例题 [\[USACO07DEC\] 手链 Charm Bracelet](https://www.luogu.org/problemnew/show/P2871)" - 本题题意可概括为——N 物体,放入容量为 M 的背包,要求使总价值最大。由于每个物体只有 2 种情况——取与不取,正如二进制中的 0 和 1——这类问题便被称为 “0-1 背包问题”。 +??? note " 例题[\[USACO07DEC\]手链 Charm Bracelet](https://www.luogu.org/problemnew/show/P2871)" + 本题题意可概括为——N 物体,放入容量为 M 的背包,要求使总价值最大。由于每个物体只有 2 种情况——取与不取,正如二进制中的 0 和 1——这类问题便被称为“0-1 背包问题”。 ## 0-1 背包 -例题中已知条件有第 i 个物体的体积 v[i] 和价值 w[i], 背包总容量 +例题中已知条件有第 i 个物体的体积 v[i]和价值 w[i], 背包总容量 显而易见的是,可以计算总价值的,只有已经放入背包的物体,因此该题中对 "是否为最大值" 的判断是建立在 "已经放入背包之中" 的基础之上的 -已知对于一个容量为 v1,可以放置第 1 到第 i 件物体的背包,其最大总价值很明显等于容量为 v1 的背包,放有第 1 到第 (i-1) 件物体时的最大值 (第 i 件物体不取时) 或者是容量为 v1-v[i]的背包,放有第 1 到第 (i-1) 件物体时的最大值 + w[i](第i件物体取时) +已知对于一个容量为 v1,可以放置第 1 到第 i 件物体的背包,其最大总价值很明显等于容量为 v1 的背包,放有第 1 到第 (i-1) 件物体时的最大值(第 i 件物体不取时)或者是容量为 v1-v[i]的背包,放有第 1 到第 (i-1) 件物体时的最大值 + w[i](第i件物体取时) 由此可以得出状态转移方程 @@ -24,13 +24,13 @@ for (int i = 1; i <= v1; i++) for (int l = 0; l <= v1 - i; l++) dp[l + i] = max(dp[l] + w[i], dp[l + i]); ``` -按照正确的思路,写出了这样的核心代码,然后就可以提交...... +按照正确的思路,写出了这样的核心代码,然后就可以提交…… 错! 让我们再回头看一下代码,i 表示当前判断的是第 i 个物体,l 则穷举体积,可是注意一个地方——l 是从 0 到 v1-v[i] -这意味着什么呢?举个栗子,可能在体积为 (l) 处取物体 i 新的 dp 值存到体积为 (l+v[i]) 处, 而在体积为 (l+v[i]) 处,物体 i 再次被取 +这意味着什么呢?举个栗子,可能在体积为 (l) 处取物体 i 新的 dp 值存到体积为 (l+v[i]) 处,而在体积为 (l+v[i]) 处,物体 i 再次被取 所以,当以 0~v1-v[i]的顺序穷举时,物体实际上可能被加入多遍,这显然与题意不符 diff --git a/docs/dp/dag.md b/docs/dp/dag.md index 5e53f1a5..8e4fcdfa 100644 --- a/docs/dp/dag.md +++ b/docs/dp/dag.md @@ -4,14 +4,14 @@ DAG 即[有向无环图](/graph/dag),一些实际问题中的二元关系都 以这道题为例子,来分析一下 DAG 建模的过程。 -??? note " 例题 [UVa 437 巴比伦塔 The Tower of Babylon](https://cn.vjudge.net/problem/UVA-437)" +??? note " 例题[UVa 437 巴比伦塔 The Tower of Babylon](https://cn.vjudge.net/problem/UVA-437)" 有 $n (n\leqslant 30)$ 种砖块,已知三条边长,每种都有无穷多个。要求选一些立方体摞成一根尽量高的柱子(每个砖块可以自行选择一条边作为高),使得每个砖块的底面长宽分别严格小于它下方砖块的底面长宽,求塔的最大高度。 ### 建立 DAG 由于每个砖块的底面长宽分别严格小于它下方砖块的底面长宽,因此不难将这样一种关系作为建图的依据,而本题也就转化为最长路问题。 -也就是说如果砖块 $j$ 能放在砖块 $i$ 上,那么 $i$ 和 $j$ 之间存在一条边 $(i, j)$,且边权就是砖块 $j$ 所选取的高。 +也就是说如果砖块 $j$ 能放在砖块 $i$ 上,那么 $i$ 和 $j$ 之间存在一条边 $(i, j)$ ,且边权就是砖块 $j$ 所选取的高。 本题的另一个问题在于每个砖块的高有三种选法,怎样建图更合适呢? @@ -19,8 +19,7 @@ DAG 即[有向无环图](/graph/dag),一些实际问题中的二元关系都 初始的起点是大地,大地的底面是无穷大的,则大地可达任意砖块,当然我们写程序时不必特意写上无穷大。 -假设有两个砖块,三条边分别为 $31, 41, 59$ 和 $33, 83, 27$ ,那么整张 DAG 应该如下图所示。 -![](./images/dag-babylon.png) +假设有两个砖块,三条边分别为 $31, 41, 59$ 和 $33, 83, 27$ ,那么整张 DAG 应该如下图所示。![](./images/dag-babylon.png) 图中蓝实框所表示的是一个砖块拆解得到的一组砖块,之所以用 $\{\}$ 表示底面边长,是因为砖块一旦选取了高,底面边长就是无序的。 @@ -36,13 +35,13 @@ DAG 即[有向无环图](/graph/dag),一些实际问题中的二元关系都 显然,砖块一旦选取了高,那么这块砖块上最大能放的高度是确定的。 -某个砖块 $i$ 有三种堆叠方式分别记为 $0, 1, 2$,那么对于砖块 $i$ 和其堆叠方式 $r$ 来说则有如下转移方程 +某个砖块 $i$ 有三种堆叠方式分别记为 $0, 1, 2$ ,那么对于砖块 $i$ 和其堆叠方式 $r$ 来说则有如下转移方程 -$d(i, r) = \max\left\{d(j, r') + h'\right\}$ + $d(i, r) = \max\left\{d(j, r') + h'\right\}$ -其中 $j$ 是所有那些在砖块 $i$ 以 $r$ 方式堆叠时可放上的砖块,$r'$ 对应 $j$ 此时的摆放方式,也就确定了此时唯一的高度 $h'$。 +其中 $j$ 是所有那些在砖块 $i$ 以 $r$ 方式堆叠时可放上的砖块, $r'$ 对应 $j$ 此时的摆放方式,也就确定了此时唯一的高度 $h'$ 。 -在实际编写时,将所有 $d(i, r)$ 都初始化为 $-1$,表示未计算过。 +在实际编写时,将所有 $d(i, r)$ 都初始化为 $-1$ ,表示未计算过。 在试图计算前,如果发现已经计算过,直接返回保存的值;否则就按步计算,并保存。 diff --git a/docs/dp/index.md b/docs/dp/index.md index 273ab387..536f8a0f 100644 --- a/docs/dp/index.md +++ b/docs/dp/index.md @@ -1,7 +1,7 @@ 动态规划应用于子问题重叠的情况: 1. 要去刻画最优解的结构特征; -2. 尝试递归地定义最优解的值(就是我们常说的考虑从 $i - 1$ 转移到 $i$); +2. 尝试递归地定义最优解的值(就是我们常说的考虑从 $i - 1$ 转移到 $i$ ); 3. 计算最优解; 4. 利用计算出的信息构造一个最优解。 @@ -17,8 +17,8 @@ 动态规划的两种实现方法: -- 带备忘的自顶向下法 (记忆化搜索); -- 自底向上法 (将子问题按规模排序,类似于递推)。 +- 带备忘的自顶向下法(记忆化搜索); +- 自底向上法(将子问题按规模排序,类似于递推)。 算导用子问题图上按照逆拓扑序求解问题,引出记忆化搜索。 @@ -28,7 +28,7 @@ 给出 $n$ 个矩阵的序列,希望计算他们的乘积,问最少需要多少次乘法运算? -(认为 $p \times q$ 的矩阵与 $q\times r$ 的矩阵相乘代价是 $p\times q\times r$。) +(认为 $p \times q$ 的矩阵与 $q\times r$ 的矩阵相乘代价是 $p\times q\times r$ 。) 完全括号化方案是指要给出谁先和谁乘。 @@ -73,7 +73,7 @@ 子序列允许不连续。 -每个 $c[i][j]$ 只依赖于 $c[i - 1][j]$、$c[i][j - 1]$ 和 $c[i - 1][j - 1]$。 +每个 $c[i][j]$ 只依赖于 $c[i - 1][j]$ 、 $c[i][j - 1]$ 和 $c[i - 1][j - 1]$ 。 记录最优方案的时候可以不需要额外建表(优化空间),因为重新选择一遍(转移过程)也是 $O(1)$ 的。 @@ -85,7 +85,7 @@ 由于每个节点的深度都增加了 1,这棵子树的期望搜索代价的增加值应为所有概率之和。 -> tD / eD 动态规划: +> tD/eD 动态规划: > 状态空间是 $O(n^t)$ 的,每一项依赖其他 $O(n^e)$ 项。 ## 最长连续不下降子序列 @@ -114,7 +114,7 @@ int dp() { ### 最简单的第一种 -$O\left(n^2\right)$的算法。每一次重头扫描找出最佳答案。 + $O\left(n^2\right)$ 的算法。每一次重头扫描找出最佳答案。 ```c++ int a[MAXN], d[MAXN]; @@ -134,19 +134,19 @@ int dp() { ### 稍复杂的第二种 -$O\left(n log n\right)$ 的算法,参考了这篇文章。 + $O\left(n log n\right)$ 的算法,参考了这篇文章。 -首先,定义 $a_1 \dots a_n$ 为原始序列,$d$ 为当前的不下降子序列,$len$ 为子序列的长度,那么 $d_{len}$ 就是长度为 $len$ 的不下降子序列末尾元素。 +首先,定义 $a_1 \dots a_n$ 为原始序列, $d$ 为当前的不下降子序列, $len$ 为子序列的长度,那么 $d_{len}$ 就是长度为 $len$ 的不下降子序列末尾元素。 -初始化:$d_1=a_1,len=1$。 +初始化: $d_1=a_1,len=1$ 。 现在我们已知最长的不下降子序列长度为 1,那么我们让 $i$ 从 2 到 $n$ 循环,依次求出前 $i$ 个元素的最长不下降子序列的长度,循环的时候我们只需要维护好 $d$ 这个数组还有 $len$ 就可以了。**关键在于如何维护。** -考虑进来一个元素$a_i$: +考虑进来一个元素 $a_i$ : -1. 元素大于 $d_{len}$,直接 $d_{++len}=a_i$ 即可,这个比较好理解。 -2. 元素等于 $d_{len}$,因为前面的元素都小于它,所以这个元素可以直接抛弃。 -3. 元素小于 $d_{len}$,找到**第一个**大于它的元素,插入进去,其他小于它的元素不要。 +1. 元素大于 $d_{len}$ ,直接 $d_{++len}=a_i$ 即可,这个比较好理解。 +2. 元素等于 $d_{len}$ ,因为前面的元素都小于它,所以这个元素可以直接抛弃。 +3. 元素小于 $d_{len}$ ,找到**第一个**大于它的元素,插入进去,其他小于它的元素不要。 那么代码如下: @@ -165,7 +165,7 @@ while (dp[ans] != mx) ++ans; ### DAG 中的最长简单路径 -$dp[i] = \max(dp[j] + 1), ((j, i) \in E)$ + $dp[i] = \max(dp[j] + 1), ((j, i) \in E)$ ### 最长回文子序列 @@ -177,11 +177,11 @@ dp[i + 1][i + len - 1] + 2, & \text{if $s[i] = s[i + len]$} \\[2ex] \end{cases} $$ -边界:$dp[i][i] = 1$。 +边界: $dp[i][i] = 1$ 。 -注意:$dp[i][j]$ 表示的是闭区间。 +注意: $dp[i][j]$ 表示的是闭区间。 -也可以转化为 LCS 问题,只需要把 $a$ 串反转当做 $b$,对 $a$ 和 $b$ 求 LCS 即可。 +也可以转化为 LCS 问题,只需要把 $a$ 串反转当做 $b$ ,对 $a$ 和 $b$ 求 LCS 即可。 证明在[这里](https://www.zhihu.com/question/34580085/answer/59539708)。 @@ -189,19 +189,19 @@ $$ ### 最长回文子串 -$O(n^2)$:$dp[i] = \max(dp[j] + 1), s(j + 1 \cdots i)$ 是回文 + $O(n^2)$ : $dp[i] = \max(dp[j] + 1), s(j + 1 \cdots i)$ 是回文 -$O(n)$: Manacher + $O(n)$ :Manacher -$p[i]$ 表示从 $i$ 向两侧延伸(当然要保证两侧对应位置相等)的最大长度。 + $p[i]$ 表示从 $i$ 向两侧延伸(当然要保证两侧对应位置相等)的最大长度。 -为了处理方便,我们把原串每两个字符之间加一个(不包含在原串中的)`#`,开头加一个 `$`。 +为了处理方便,我们把原串每两个字符之间加一个(不包含在原串中的) `#` ,开头加一个 `$` 。 这样得到的回文串长度就保证是奇数了 -考虑如果按顺序得到了 $p[1 \cdots i - 1]$,如何计算 $p[i]$ 的值? +考虑如果按顺序得到了 $p[1 \cdots i - 1]$ ,如何计算 $p[i]$ 的值? -如果之前有一个位置比如说是 $id$,有 $p[id] + id > i$ 那么 $i$ 这个位置是被覆盖了的,根据 $id$ 处的对称性,我们找 $p[id \times 2 - i]$ 延伸的部分被 $p[id]$ 延伸的部分所覆盖的那段,显然这段对称回去之后是可以从 $i$ 处延伸出去的长度。 +如果之前有一个位置比如说是 $id$ ,有 $p[id] + id > i$ 那么 $i$ 这个位置是被覆盖了的,根据 $id$ 处的对称性,我们找 $p[id \times 2 - i]$ 延伸的部分被 $p[id]$ 延伸的部分所覆盖的那段,显然这段对称回去之后是可以从 $i$ 处延伸出去的长度。 如果找不到呢?就先让 $p[i] = 1$ 吧。 @@ -225,7 +225,7 @@ upd:其实是[程设期末推荐练习](https://ir1d.cf/2018/06/23/cssx/程设 #### 思路一 -$dp[i][j]$ 表示 $1 \cdots i$ 和 $1 \cdots j$ 两条路径。 + $dp[i][j]$ 表示 $1 \cdots i$ 和 $1 \cdots j$ 两条路径。 我们可以人为要求 $1 \cdots i$ 是更快的那一条路径。 @@ -233,34 +233,34 @@ $dp[i][j]$ 表示 $1 \cdots i$ 和 $1 \cdots j$ 两条路径。 如果是分给快的那条: -$dp[i][j] = \min(dp[i - 1][j] + dis[i - 1][i]),\ j = 1 \cdots i$ + $dp[i][j] = \min(dp[i - 1][j] + dis[i - 1][i]),\ j = 1 \cdots i$ 如果是慢的,原来是慢的那条就变成了快的,所以另一条是到 $i - 1$ 那个点: -$dp[i][j] = \min(dp[i - 1][j] + dis[j][i]),\ j = 1 \cdots i$ + $dp[i][j] = \min(dp[i - 1][j] + dis[j][i]),\ j = 1 \cdots i$ -答案是 $\min(dp[n][i] + dis[n][i])$。 -(从一开始编号,终点是 $n$) +答案是 $\min(dp[n][i] + dis[n][i])$ 。 +(从一开始编号,终点是 $n$ ) 代码: #### 思路二 -把 $dp[i][j]$ 定义反过来,不是 $1 \cdots i$ 和 $1 \cdots j$。 +把 $dp[i][j]$ 定义反过来,不是 $1 \cdots i$ 和 $1 \cdots j$ 。 -改成是 $i..n$ 和 $j \cdots n$,不要求哪个更快。 +改成是 $i..n$ 和 $j \cdots n$ ,不要求哪个更快。 这样的转移更好写: -我们记 $k = \max(i, j) + 1$ +我们记 $k = \max(i, j) + 1$ -$k$ 这个点肯定在两条路中的一个上,$dp[i][j]$ 取两种情况的最小值即可。 + $k$ 这个点肯定在两条路中的一个上, $dp[i][j]$ 取两种情况的最小值即可。 -$dp[i][j] = \min(dp[i][k] + dis[k][j], dp[k][j] + dis[i][k])$ + $dp[i][j] = \min(dp[i][k] + dis[k][j], dp[k][j] + dis[i][k])$ -边界是:$dp[i][n] = dp[n][i] = dis[n][i]$。 +边界是: $dp[i][n] = dp[n][i] = dis[n][i]$ 。 -答案是 $dp[1][1]$。 +答案是 $dp[1][1]$ 。 ### 整齐打印 @@ -268,7 +268,7 @@ $dp[i][j] = \min(dp[i][k] + dis[k][j], dp[k][j] + dis[i][k])$ 注意到实际问题要求单词不能打乱顺序,所以就好做了起来。**不要把题目看复杂。** -$dp[i] = \min(dp[j] + cost[j][i])$ + $dp[i] = \min(dp[j] + cost[j][i])$ 不知道这样可不可做:有 $n$ 个单词,可以不按顺序打印,问怎么安排,使得把他们打印成 $m$ 行之后,每行的空格之和最小。 @@ -296,16 +296,15 @@ insert : -2 没有上司的舞会。 -$dp[x][0]$ 是没去,$dp[x][1]$ 是去了。 + $dp[x][0]$ 是没去, $dp[x][1]$ 是去了。 -$dp[u][0] = \max(dp[v][0], dp[v][1]), v \in son(u)$ + $dp[u][0] = \max(dp[v][0], dp[v][1]), v \in son(u)$ -$dp[u][1] = w[u] + dp[v][0], v \in son(u)$ + $dp[u][1] = w[u] + dp[v][0], v \in son(u)$ ### 译码算法 -[Viterbi algorithm](https://en.wikipedia.org/wiki/Viterbi_algorithm) -之前写词性标注的时候有用到,好像用在输入法里面也是类似的。 +[Viterbi algorithm](https://en.wikipedia.org/wiki/Viterbi_algorithm)之前写词性标注的时候有用到,好像用在输入法里面也是类似的。 本题中用来实现语音识别,其实就是找一条对应的概率最大的路径。 @@ -319,9 +318,9 @@ ref: 限制:要求相邻两行中删除的像素必须位于同一列或相邻列。 -$dp[i][j] = \min(dp[i - 1][j], dp[i - 1][j - 1], dp[i - 1][j + 1]) + cost[i][j]$ + $dp[i][j] = \min(dp[i - 1][j], dp[i - 1][j - 1], dp[i - 1][j + 1]) + cost[i][j]$ -边界:$dp[1][i] = cost[1][i]$。 +边界: $dp[1][i] = cost[1][i]$ 。 ### 字符串拆分 @@ -329,11 +328,11 @@ $dp[i][j] = \min(dp[i - 1][j], dp[i - 1][j - 1], dp[i - 1][j + 1]) + cost[i][j]$ 等价于之前那个最优二叉搜索树。 -$dp[i][j] = \min(dp[i][k] + dp[k][j]) + l[j] - l[i] + 1,\ k = i + 1 \cdots j - 1$ + $dp[i][j] = \min(dp[i][k] + dp[k][j]) + l[j] - l[i] + 1,\ k = i + 1 \cdots j - 1$ 注意 $l[i]$ 表示的是第 i 个切分点的位置。 -边界:$dp[i][i] = 0$。 +边界: $dp[i][i] = 0$ 。 就按照区间 dp 的姿势来写就好了。 @@ -351,13 +350,13 @@ $dp[i][j] = \min(dp[i][k] + dp[k][j]) + l[j] - l[i] + 1,\ k = i + 1 \cdots j - 1 生产多了少了都有额外的成本,问怎么安排生产策略使得额外的成本尽可能地少。 -$cost[i][j]$ 表示剩下 $i$ 个月,开始的时候有 $j$ 台库存的最小成本。 + $cost[i][j]$ 表示剩下 $i$ 个月,开始的时候有 $j$ 台库存的最小成本。 ### 签约棒球自由球员 -$v[i][j]$ 是考虑 $i$ 之后的位置,总费用为 $x$ 的最大收益。 + $v[i][j]$ 是考虑 $i$ 之后的位置,总费用为 $x$ 的最大收益。 diff --git a/docs/dp/interval.md b/docs/dp/interval.md index e5bfd131..0b9edd91 100644 --- a/docs/dp/interval.md +++ b/docs/dp/interval.md @@ -1,37 +1,37 @@ ## 什么是区间 DP? -区间类动态规划是线性动态规划的扩展,它在分阶段地划分问题时,与阶段中元素出现的顺序和由前一阶段的哪些元素合并而来由很大的关系。令状态 $f(i,j)$ 表示将下标位置 $i$ 到 $j$ 的所有元素合并能获得的价值的最大值,那么 $f(i,j)=\max\{f(i,k)+f(k+1,j)+cost\}$, $cost$ 为将这两组元素合并起来的代价。 +区间类动态规划是线性动态规划的扩展,它在分阶段地划分问题时,与阶段中元素出现的顺序和由前一阶段的哪些元素合并而来由很大的关系。令状态 $f(i,j)$ 表示将下标位置 $i$ 到 $j$ 的所有元素合并能获得的价值的最大值,那么 $f(i,j)=\max\{f(i,k)+f(k+1,j)+cost\}$ , $cost$ 为将这两组元素合并起来的代价。 区间 DP 的特点: -** 合并 ** :即将两个或多个部分进行整合,当然也可以反过来; +**合并**:即将两个或多个部分进行整合,当然也可以反过来; -** 特征 ** :能将问题分解为能两两合并的形式; +**特征**:能将问题分解为能两两合并的形式; -** 求解 ** :对整个问题设最优值,枚举合并点,将问题分解为左右两个部分,最后合并两个部分的最优值得到原问题的最优值。 +**求解**:对整个问题设最优值,枚举合并点,将问题分解为左右两个部分,最后合并两个部分的最优值得到原问题的最优值。 -??? note " 例题 [洛谷 P1880 \[NOI1995\] 石子合并](https://www.luogu.org/problemnew/show/P1880)" - 题目大意:在一个环上有 $n$ 个数 $a_1,a_2,...,a_n$,进行 $n-1$ 次合并操作,每次操作将相邻的两堆合并成一堆,能获得新的一堆中的石子数量的和的得分。你需要最大化你的得分。 +??? note " 例题[洛谷 P1880\[NOI1995\]石子合并](https://www.luogu.org/problemnew/show/P1880)" + 题目大意:在一个环上有 $n$ 个数 $a_1,a_2,...,a_n$ ,进行 $n-1$ 次合并操作,每次操作将相邻的两堆合并成一堆,能获得新的一堆中的石子数量的和的得分。你需要最大化你的得分。 考虑不在环上,而在一条链上的情况。 令 $f(i,j)$ 表示将区间 $[i,j]$ 内的所有石子合并到一起的最大得分。 -写出 ** 状态转移方程 ** : $f(i,j)=max\{f(i,k)+f(k+1,j)+\sum_{t=i}^{j} a_t \}~(i\le k 想体验把暴搜改改就是正解的快感吗? 想体验状压 dp 看似状态多到爆炸实际一跑却嗷嗷快 (实际有效的状态数很少) 的荣耀吗? 记忆化搜索, 符合您的需求! 只要 998 , 记忆化搜索带回家! 记忆化搜索, 记忆化搜索, 再说一遍, 记忆化搜索! +> 想体验把暴搜改改就是正解的快感吗?想体验状压 dp 看似状态多到爆炸实际一跑却嗷嗷快(实际有效的状态数很少)的荣耀吗?记忆化搜索,符合您的需求!只要 998 , 记忆化搜索带回家!记忆化搜索,记忆化搜索,再说一遍,记忆化搜索! * * * ## 记忆化搜索是啥 -好,就以 [洛谷 P1048 采药](https://www.luogu.org/problemnew/show/P1048) 为例,我不会动态规划,只会搜索,我就会直接写一个粗暴的 [DFS](/search/dfs) : +好,就以[洛谷 P1048 采药](https://www.luogu.org/problemnew/show/P1048)为例,我不会动态规划,只会搜索,我就会直接写一个粗暴的[DFS](/search/dfs): -- 注: 为了方便食用, 本文中所有代码省略头文件 +- 注:为了方便食用,本文中所有代码省略头文件 ```cpp int n, t; @@ -34,17 +34,17 @@ int main() { emmmmmm....... $30$ 分 -然后我心血来潮, 想不借助任何 "外部变量"(就是 dfs 函数外且 ** 值随 dfs 运行而改变的变量 **), 比如 ans +然后我心血来潮,想不借助任何 "外部变量"(就是 dfs 函数外且**值随 dfs 运行而改变的变量**), 比如 ans -把 ans 删了之后就有一个问题: 我们拿什么来记录答案? +把 ans 删了之后就有一个问题:我们拿什么来记录答案? -答案很简单: +答案很简单: -**返回值!** +**返回值!** -此时 $dfs(pos,tleft)$ 返回在时间 $tleft$ 内采集 ** 后 **$pos$ 个草药, 能获得的最大收益 +此时 $dfs(pos,tleft)$ 返回在时间 $tleft$ 内采集**后** $pos$ 个草药,能获得的最大收益 -不理解就看看代码吧: +不理解就看看代码吧: ```cpp int n, time; @@ -66,21 +66,21 @@ int main() { ~~emmmmmm....... 还是 ${30}$ 分~~ -但这个时候, 我们的程序已经不依赖任何外部变量了. +但这个时候,我们的程序已经不依赖任何外部变量了。 -然后我非常无聊, 将所有 dfs 的返回值都记录下来, 竟然发现...... +然后我非常无聊,将所有 dfs 的返回值都记录下来,竟然发现…… -**震惊, 对于相同的 pos 和 tleft,dfs 的返回值总是相同的!** +**震惊,对于相同的 pos 和 tleft,dfs 的返回值总是相同的!** -想一想也不奇怪, 因为我们的 dfs 没有依赖任何外部变量. +想一想也不奇怪,因为我们的 dfs 没有依赖任何外部变量。 -旁白: 像 $tcost[103]$,$mget[103]$ 这种东西不算是外部变量, 因为她们在 dfs 过程中不变. +旁白:像 $tcost[103]$ , $mget[103]$ 这种东西不算是外部变量,因为她们在 dfs 过程中不变。 -然后? +然后? -开个数组 $mem$ , 记录下来每个 $dfs(pos,tleft)$ 的返回值. 刚开始把 $mem$ 中每个值都设成 $-1$ (代表没访问过). 每次刚刚进入一个 dfs 前 (我们的 dfs 是递归调用的嘛), 都检测 $mem[pos][tleft]$ 是否为 $-1$ , 如果是就正常执行并把答案记录到 $mem$ 中, 否则? +开个数组 $mem$ , 记录下来每个 $dfs(pos,tleft)$ 的返回值。刚开始把 $mem$ 中每个值都设成 $-1$ (代表没访问过). 每次刚刚进入一个 dfs 前(我们的 dfs 是递归调用的嘛), 都检测 $mem[pos][tleft]$ 是否为 $-1$ , 如果是就正常执行并把答案记录到 $mem$ 中,否则? -**直接返回 $mem$ 中的值!** +**直接返回 $mem$ 中的值!** ```cpp int n, t; @@ -103,51 +103,51 @@ int main() { } ``` -此时 $mem$ 的意义与 dfs 相同: +此时 $mem$ 的意义与 dfs 相同: -> 在时间 $tleft$ 内采集 ** 后 ** $pos$ 个草药, 能获得的最大收益 +> 在时间 $tleft$ 内采集**后** $pos$ 个草药,能获得的最大收益 这能 ac ? -能.**这就是 "采药" 那题的 AC 代码** +能。**这就是 "采药" 那题的 AC 代码** 好我们 yy 出了记忆化搜索 -#### 总结一下记忆化搜索是啥: +#### 总结一下记忆化搜索是啥: -- 不依赖任何 **外部变量** -- 答案以返回值的形式存在, 而不能以参数的形式存在 (就是不能将 dfs 定义成 $dfs(pos ,tleft , nowans )$, 这里面的 nowans 不符合要求). -- 对于相同一组参数, dfs 返回值总是相同的 +- 不依赖任何**外部变量** +- 答案以返回值的形式存在,而不能以参数的形式存在(就是不能将 dfs 定义成 $dfs(pos ,tleft , nowans )$ , 这里面的 nowans 不符合要求). +- 对于相同一组参数,dfs 返回值总是相同的 * * * -## 记忆化搜索与动态规划的关系: +## 记忆化搜索与动态规划的关系: -有人会问: 记忆化搜索难道不是搜索? +有人会问:记忆化搜索难道不是搜索? -是搜索. 但个人认为她更像 dp : +是搜索。但个人认为她更像 dp : -不信你看 $mem$ 的意义: +不信你看 $mem$ 的意义: -> 在时间 $tleft$ 内采集 ** 后 ** $pos$ 个草药, 能获得的最大收益 +> 在时间 $tleft$ 内采集**后** $pos$ 个草药,能获得的最大收益 -这不就是 dp 的状态? +这不就是 dp 的状态? -由上面的代码中可以看出: +由上面的代码中可以看出: -> $mem[pos][tleft] = max(mem[pos+1][tleft-tcost[pos]]+mget[pos]\ ,\ mem[pos+1][tleft])$ +> $mem[pos][tleft] = max(mem[pos+1][tleft-tcost[pos]]+mget[pos]\ ,\ mem[pos+1][tleft])$ -这不就是 dp 的状态转移? +这不就是 dp 的状态转移? -个人认为: +个人认为: -> 记忆化搜索约等于动态规划,**(印象中) 任何一个 dp 方程都能转为记忆化搜索 ** +> 记忆化搜索约等于动态规划,**(印象中)任何一个 dp 方程都能转为记忆化搜索** -大部分记忆化搜索的状态 / 转移方程与 dp 都一样, 时间复杂度 / 空间复杂度与 ** 不加优化的 ** dp 完全相同 +大部分记忆化搜索的状态/转移方程与 dp 都一样,时间复杂度/空间复杂度与**不加优化的**dp 完全相同 -比如: +比如: -$dp[i][j][k] = dp[i+1][j+1][k-a[j]] + dp[i+1][j][k]$ + $dp[i][j][k] = dp[i+1][j+1][k-a[j]] + dp[i+1][j][k]$ 转为 @@ -174,9 +174,9 @@ int main() { 2. 根据他们写出 dfs 函数 3. 添加记忆化数组 -举例: +举例: -$dp[i] = max\{dp[j]+1\}\quad 1 \leq j < i \text{且}a[j]1$ 个点的情况. +但是!如果我们用记忆化搜索,就可以避免到很多无用的状态,比如 $pos$ 为起点却已经经过了 $>1$ 个点的情况。 -- 不需要注意转移顺序 (这里的 "转移顺序" 指正常 dp 中 for 循环的嵌套顺序以及循环变量是递增还是递减) +- 不需要注意转移顺序(这里的 "转移顺序" 指正常 dp 中 for 循环的嵌套顺序以及循环变量是递增还是递减) -举例: 用常规 dp 写 "合并石子" 需要先枚举区间长度然后枚举起点, 但记忆化搜索直接枚举断点 (就是枚举当前区间由哪两个区间合并而成) 然后递归下去就行 +举例:用常规 dp 写 "合并石子" 需要先枚举区间长度然后枚举起点,但记忆化搜索直接枚举断点(就是枚举当前区间由哪两个区间合并而成)然后递归下去就行 -- 边界情况非常好处理, 且能有效防止数组访问越界 -- 有些 dp (如区间 dp) 用记忆化搜索写很简单但正常 dp 很难 -- 记忆化搜索天生携带搜索天赋, 可以使用技能 "剪枝"! +- 边界情况非常好处理,且能有效防止数组访问越界 +- 有些 dp(如区间 dp) 用记忆化搜索写很简单但正常 dp 很难 +- 记忆化搜索天生携带搜索天赋,可以使用技能 "剪枝"! -缺点: +缺点: -- 致命伤: 不能滚动数组! +- 致命伤:不能滚动数组! - 有些优化比较难加 -- 由于递归, 有时效率较低但不至于 TLE (状压 dp 除外) +- 由于递归,有时效率较低但不至于 TLE(状压 dp 除外) * * * ## 记忆化搜索的注意事项 -- 千万别忘了加记忆化! (别笑, 认真的 -- 边界条件要加在检查当前数组值是否为非法数值 (防止越界) -- 数组不要开小了 (逃 +- 千万别忘了加记忆化!(别笑,认真的 +- 边界条件要加在检查当前数组值是否为非法数值(防止越界) +- 数组不要开小了(逃 ## 模板 diff --git a/docs/dp/number.md b/docs/dp/number.md index 6da01dcb..8a709f68 100644 --- a/docs/dp/number.md +++ b/docs/dp/number.md @@ -1,21 +1,21 @@ ## 经典题型 -数位 DP 问题往往都是这样的题型,给定一个闭区间 $[l,r]$,让你求这个区间中满足 **某种条件** 的数的总数。 +数位 DP 问题往往都是这样的题型,给定一个闭区间 $[l,r]$ ,让你求这个区间中满足**某种条件**的数的总数。 -??? note " 例题 [洛谷 P2657 \[SCOI2009\] windy 数](https://www.luogu.org/problemnew/show/P2657)" - 题目大意:给定一个区间 $[l,r]$ ,求其中满足条件 **不含前导 $0$ 且相邻两个数字相差至少为 $2$** 的数字个数。 +??? note " 例题[洛谷 P2657\[SCOI2009\]windy 数](https://www.luogu.org/problemnew/show/P2657)" + 题目大意:给定一个区间 $[l,r]$ ,求其中满足条件**不含前导 $0$ 且相邻两个数字相差至少为 $2$ **的数字个数。 -首先我们将问题转化成更加简单的形式。设 $ans_i$ 表示在区间 $[1,i]$ 中满足条件的数的数量,那么所求的答案就是 $ans_r-ans_{l-1}$。 +首先我们将问题转化成更加简单的形式。设 $ans_i$ 表示在区间 $[1,i]$ 中满足条件的数的数量,那么所求的答案就是 $ans_r-ans_{l-1}$ 。 分开求解这两个问题。 对于一个小于 $n$ 的数,它从高到低肯定出现某一位,使得这一位上的数值小于 $n$ 这一位上对应的数值。而之前的所有位都和 $n$ 上的位相等。 -有了这个性质,我们可以定义 $f(i,st,op)$ 表示当前将要考虑的是从高到低的第 $i$ 位,当前该前缀的状态为 $st$ 且前缀和当前求解的数字的大小关系是 $op$ ($op=1$ 表示等于,$op=0$ 表示小于)时的数字个数。在本题中,这个前缀的状态就是上一位的值,因为当前将要确定的位不能取哪些数只和上一位有关。在其他题目中,这个值可以是:前缀的数字和,前缀所有数字的 $\gcd$,该前缀取模某个数的余数,也有两种或多种合用的情况。 +有了这个性质,我们可以定义 $f(i,st,op)$ 表示当前将要考虑的是从高到低的第 $i$ 位,当前该前缀的状态为 $st$ 且前缀和当前求解的数字的大小关系是 $op$ ( $op=1$ 表示等于, $op=0$ 表示小于)时的数字个数。在本题中,这个前缀的状态就是上一位的值,因为当前将要确定的位不能取哪些数只和上一位有关。在其他题目中,这个值可以是:前缀的数字和,前缀所有数字的 $\gcd$ ,该前缀取模某个数的余数,也有两种或多种合用的情况。 -写出 **状态转移方程** : $f(i,st,op)=\sum_{i=1}^{maxx} f(i+1,k,op=1~ \operatorname{and}~ i=maxx )\quad (|st-k|\ge 2)$ +写出**状态转移方程**: $f(i,st,op)=\sum_{i=1}^{maxx} f(i+1,k,op=1~ \operatorname{and}~ i=maxx )\quad (|st-k|\ge 2)$ -这里的 $k$ 就是当前枚举的下一位的值,而 $maxx$ 就是当前能取到的最高位。因为如果 $op=1$,那么你在这一位上取的值一定不能大于求解的数字上该位的值,否则则没有限制。 +这里的 $k$ 就是当前枚举的下一位的值,而 $maxx$ 就是当前能取到的最高位。因为如果 $op=1$ ,那么你在这一位上取的值一定不能大于求解的数字上该位的值,否则则没有限制。 我们发现,尽管前缀所选择的状态不同,而 $f$ 的三个参数相同,答案就是一样的。为了防止这个答案被计算多次,可以使用记忆化搜索的方式实现。 @@ -52,15 +52,15 @@ int solve(int x) { ## 几道练习题 -[BZOJ 3679 数字之积 ](https://www.lydsy.com/JudgeOnline/problem.php?id=3679) +[BZOJ 3679 数字之积](https://www.lydsy.com/JudgeOnline/problem.php?id=3679) -[洛谷 P2602 \[ZJOI2010\] 数字计数 ](https://www.luogu.org/problemnew/show/P2602) +[洛谷 P2602\[ZJOI2010\]数字计数](https://www.luogu.org/problemnew/show/P2602) -[洛谷 P4127 \[AHOI2009\] 同类分布 ](https://www.luogu.org/problemnew/show/P4127) +[洛谷 P4127\[AHOI2009\]同类分布](https://www.luogu.org/problemnew/show/P4127) [洛谷 P3413 SAC#1 - 萌数](https://www.luogu.org/problemnew/show/P3413) -[HDU 6148 Valley Number ](http://acm.hdu.edu.cn/showproblem.php?pid=6148) +[HDU 6148 Valley Number](http://acm.hdu.edu.cn/showproblem.php?pid=6148) [CF55D Beautiful numbers](http://codeforces.com/problemset/problem/55/D) diff --git a/docs/dp/optimization.md b/docs/dp/optimization.md index f3bd71ea..34bf5622 100644 --- a/docs/dp/optimization.md +++ b/docs/dp/optimization.md @@ -1,15 +1,15 @@ -本章主要讲解动态规划的几种基础 ** 优化 ** 方法。 +本章主要讲解动态规划的几种基础**优化**方法。 ## 二进制优化解多重背包 -??? note " 例题 [经典问题 - 多重背包](/dp/backpack/#_3)" - 题目大意:有 $n$ 种物品,每种物品有 $a_i$ 件,购买一件这种物品的费用为 $c_i$,价值为 $v_i$。有一个容量为 $t$ 的背包,现在让你找到最优的一种方案,使得装入背包的物品的总价值最大。 +??? note " 例题[经典问题 - 多重背包](/dp/backpack/#_3)" + 题目大意:有 $n$ 种物品,每种物品有 $a_i$ 件,购买一件这种物品的费用为 $c_i$ ,价值为 $v_i$ 。有一个容量为 $t$ 的背包,现在让你找到最优的一种方案,使得装入背包的物品的总价值最大。 考虑常规的动规方式,定义 $f_{i,j}$ 为当前考虑到第 $i$ 个物品,背包容量为 $j$ 所能获得的最大价值。 -状态转移方程为, $f_{i,j}=\max\{f_{i-1,j},f_{i-1,j-c_i}+v_i\}$。 +状态转移方程为, $f_{i,j}=\max\{f_{i-1,j},f_{i-1,j-c_i}+v_i\}$ 。 -对于 ** 每件 ** 物品,都要这样循环一次,时间复杂度为 $t\times \sum_{i=1}^n a_i$,某些时候可能不可接受,需要优化。 +对于**每件**物品,都要这样循环一次,时间复杂度为 $t\times \sum_{i=1}^n a_i$ ,某些时候可能不可接受,需要优化。 考虑这样一种情况,如果我们有 $17$ 个硬币,要去买 $1$ 到 $17$ 元钱的物品,只需将这些硬币打包成 $1,2,4,8$ 和 $2$ 这样的几包。前面的 $4$ 包能保证覆盖 $1$ 到 $15$ 所有的情况,最后一包在之前的基础上再加上一个值,能保证实现支付的时候取整包,肯定能保证支付。这就是二进制优化的原理和基本思想。 @@ -36,31 +36,31 @@ for (int i = 1; i <= cur; i++) ## 单调队列 & 单调栈优化 -学习本节前,请务必先学习 [单调队列](/ds/monotonous-queue/)。 +学习本节前,请务必先学习[单调队列](/ds/monotonous-queue/)。 -??? note " 例题 [CF372C Watching Fireworks is Fun](http://codeforces.com/problemset/problem/372/C)" - 题目大意:城镇中有 $n$ 个位置,有 $m$ 个烟花要放。第 $i$ 个烟花放出的时间记为 $t_i$,放出的位置记为 $a_i$。如果烟花放出的时候,你处在位置 $x$,那么你将收获 $b_i-|a_i-x|$ 点快乐值。 +??? note " 例题[CF372C Watching Fireworks is Fun](http://codeforces.com/problemset/problem/372/C)" + 题目大意:城镇中有 $n$ 个位置,有 $m$ 个烟花要放。第 $i$ 个烟花放出的时间记为 $t_i$ ,放出的位置记为 $a_i$ 。如果烟花放出的时候,你处在位置 $x$ ,那么你将收获 $b_i-|a_i-x|$ 点快乐值。 初始你可在任意位置,你每个单位时间可以移动不大于 $d$ 个单位距离。现在你需要最大化你能获得的快乐值。 设 $f_{i,j}$ 表示在放第 $i$ 个烟花时,你的位置在 $j$ 所能获得的最大快乐值。 -写出 ** 状态转移方程 ** :$f_{i,j}=\max\{f_{i-1,k}+b_i-|a_i-j|\}$ +写出**状态转移方程**: $f_{i,j}=\max\{f_{i-1,k}+b_i-|a_i-j|\}$ -这里的 $k$ 是有范围的,$j-(t_{i+1}-t_i)\times d\le k\le j+(t_{i+1}-t_i)\times d$。 +这里的 $k$ 是有范围的, $j-(t_{i+1}-t_i)\times d\le k\le j+(t_{i+1}-t_i)\times d$ 。 我们尝试将状态转移方程进行变形: -由于 $\max$ 里出现了一个确定的常量 $b_i$,我们可以将它提到外面去。 +由于 $\max$ 里出现了一个确定的常量 $b_i$ ,我们可以将它提到外面去。 -$f_{i,j}=\max\{f_{i-1,k}+b_i+|a_i-j|\}=\max\{f_{i-1,k}-|a_i-j|\}+b_i$ + $f_{i,j}=\max\{f_{i-1,k}+b_i+|a_i-j|\}=\max\{f_{i-1,k}-|a_i-j|\}+b_i$ 如果确定了 $i$ 和 $j$ 的值,那么 $|a_i-j|$ 的值也是确定的,也可以将这一部分提到外面去。 -最后,式子变成了这个样子:$f_{i,j}=\max\{f_{i-1,k}-|a_i-j|\}+b_i=\max\{f_{i-1,k}\}-|a_i-j|+b_i$ +最后,式子变成了这个样子: $f_{i,j}=\max\{f_{i-1,k}-|a_i-j|\}+b_i=\max\{f_{i-1,k}\}-|a_i-j|+b_i$ -看到这一熟悉的形式,我们想到了什么?** 单调队列优化 **。由于最终式子中的 $\max$ 只和上一状态中连续的一段的最大值有关,所以我们在计算一个新的 $i$ 的状态值时候只需将原来的 $f_{i-1}$ 构造成一个单调队列,并维护单调队列,使得其能在均摊 $O(1)$ 的时间复杂度内计算出 $\max\{f_{i-1,k}\}$ 的值,从而根据公式计算出 $f_{i,j}$ 的值。 +看到这一熟悉的形式,我们想到了什么?**单调队列优化**。由于最终式子中的 $\max$ 只和上一状态中连续的一段的最大值有关,所以我们在计算一个新的 $i$ 的状态值时候只需将原来的 $f_{i-1}$ 构造成一个单调队列,并维护单调队列,使得其能在均摊 $O(1)$ 的时间复杂度内计算出 $\max\{f_{i-1,k}\}$ 的值,从而根据公式计算出 $f_{i,j}$ 的值。 -总的时间复杂度为 $O(n\times m)$。 +总的时间复杂度为 $O(n\times m)$ 。 讲完了,让我们归纳一下单调队列优化动态规划问题的基本形态:当前状态的所有值可以从上一个状态的某个连续的段的值得到,要对这个连续的段进行 RMQ 操作,相邻状态的段的左右区间满足非降的关系。 @@ -68,63 +68,63 @@ $f_{i,j}=\max\{f_{i-1,k}+b_i+|a_i-j|\}=\max\{f_{i-1,k}-|a_i-j|\}+b_i$ [洛谷 P1886 滑动窗口](https://www.luogu.org/problemnew/show/P1886) -[洛谷 P2254 \[NOI2005\] 瑰丽华尔兹](https://www.luogu.org/problemnew/show/P2254) +[洛谷 P2254\[NOI2005\]瑰丽华尔兹](https://www.luogu.org/problemnew/show/P2254) -[洛谷 P2569 \[SCOI2010\] 股票交易](https://www.luogu.org/problemnew/show/P2569) +[洛谷 P2569\[SCOI2010\]股票交易](https://www.luogu.org/problemnew/show/P2569) ## 斜率优化 -??? note " 例题 [洛谷 P3195 \[HNOI2008\] 玩具装箱 TOY](https://www.luogu.org/problemnew/show/P3195)" +??? note " 例题[洛谷 P3195\[HNOI2008\]玩具装箱 TOY](https://www.luogu.org/problemnew/show/P3195)" 令 $f_i$ 表示前 $i$ 个物品,随意分组装在任意多个容器里所能得到的最小费用。 -写出 ** 状态转移方程 ** :$f_i=max\{f_j+(pre_i-pre_i+i-j-1-L)^2\}$ ,其中 $pre_i$ 表示前 $i$ 个数的前缀和。 +写出**状态转移方程**: $f_i=max\{f_j+(pre_i-pre_i+i-j-1-L)^2\}$ ,其中 $pre_i$ 表示前 $i$ 个数的前缀和。 -换元试图简化状态转移方程式: 令 $s_i=pre_i+i,L'=L+1$,则 $f_i=f_j+(s_i-s_j-L')^2$,展开,移项得 +换元试图简化状态转移方程式:令 $s_i=pre_i+i,L'=L+1$ ,则 $f_i=f_j+(s_i-s_j-L')^2$ ,展开,移项得 -$f_i=f_j+(s_i-s_j-L')^2$ + $f_i=f_j+(s_i-s_j-L')^2$ -$f_i+2\times s_i\times (s_j+L')=f_j+s_i^2+(s_j+L')^2$ + $f_i+2\times s_i\times (s_j+L')=f_j+s_i^2+(s_j+L')^2$ -我们观察到,式子的右端的所有项都只和 $i$ 有关或只和 $j$ 有关,式子左端的第一项是我们要求的目标值,式子左端的其余项都同时和 $i$ 和 $j$ 有关。我们将这个式子看作一条直线的函数解析式,形如 $b+k\times x=y$ ,和上式一一对应。我们发现如果我们要最小化 $f_i$ ,也就是说要最小化这个直线的截距,而对于每个确定的 $i$,这个直线的斜率 $s_i$ 都是确定的。 +我们观察到,式子的右端的所有项都只和 $i$ 有关或只和 $j$ 有关,式子左端的第一项是我们要求的目标值,式子左端的其余项都同时和 $i$ 和 $j$ 有关。我们将这个式子看作一条直线的函数解析式,形如 $b+k\times x=y$ ,和上式一一对应。我们发现如果我们要最小化 $f_i$ ,也就是说要最小化这个直线的截距,而对于每个确定的 $i$ ,这个直线的斜率 $s_i$ 都是确定的。 ![](./images/optimization.png) -如图,我们将这个斜率固定的直线从下往上平移,直到有一个点在这条直线上,然后将新的点加入点集,这样肯定能保证所有的直线的斜率都是单调递升的(因为如果新的直线斜率小于斜率最大的直线,那么其一定不成被选择成为新的决策),所以我们相当于维护了一个下凸包。(如果求的是 $\max$ 那么就要维护一个 ** 上凸包 ** 。这种东西要具体情况具体分析,如果直线的斜率不满足单调性,那就要维护整个凸包 / 二分等奇技淫巧。) +如图,我们将这个斜率固定的直线从下往上平移,直到有一个点在这条直线上,然后将新的点加入点集,这样肯定能保证所有的直线的斜率都是单调递升的(因为如果新的直线斜率小于斜率最大的直线,那么其一定不成被选择成为新的决策),所以我们相当于维护了一个下凸包。(如果求的是 $\max$ 那么就要维护一个**上凸包**。这种东西要具体情况具体分析,如果直线的斜率不满足单调性,那就要维护整个凸包/二分等奇技淫巧。) 可以用单调队列维护下凸包。 ### 几道练习题 -[洛谷 P4072 \[SDOI2016\] 征途](https://www.luogu.org/problemnew/show/P4072) +[洛谷 P4072\[SDOI2016\]征途](https://www.luogu.org/problemnew/show/P4072) -[洛谷 P2120 \[ZJOI2007\] 仓库建设](https://www.luogu.org/problemnew/show/P2120) +[洛谷 P2120\[ZJOI2007\]仓库建设](https://www.luogu.org/problemnew/show/P2120) -[洛谷 P3628 \[APIO2010\] 特别行动队](https://www.luogu.org/problemnew/show/P3628) +[洛谷 P3628\[APIO2010\]特别行动队](https://www.luogu.org/problemnew/show/P3628) -[bzoj 4709 \[Jsoi2011\] 柠檬](https://www.lydsy.com/JudgeOnline/problem.php?id=4709) +[bzoj 4709\[Jsoi2011\]柠檬](https://www.lydsy.com/JudgeOnline/problem.php?id=4709) [CF311B Cats Transport](http://codeforces.com/problemset/problem/311/B) -[洛谷 P4027 \[NOI2007\] 货币兑换](https://www.luogu.org/problemnew/show/P4027) +[洛谷 P4027\[NOI2007\]货币兑换](https://www.luogu.org/problemnew/show/P4027) ## 四边形不等式优化 -??? note " 例题 [洛谷 P1880 \[NOI1995\] 石子合并](https://www.luogu.org/problemnew/show/P1880)" +??? note " 例题[洛谷 P1880\[NOI1995\]石子合并](https://www.luogu.org/problemnew/show/P1880)" 题目大意:在一个环上有 $n$ 个数,进行 $n-1$ 次合并操作,每次操作将相邻的两堆合并成一堆,能获得新的一堆中的石子数量的和的得分。你需要最大化你的得分。 -我们首先 ** 破环成链 ** ,然后进行动态规划。设 $f_{i,j}$ 表示从位置 $i$ 合并到位置 $j$ 所能得到的最大得分, $sum_i$ 为前 $i$ 堆石子数的前缀和。 +我们首先**破环成链**,然后进行动态规划。设 $f_{i,j}$ 表示从位置 $i$ 合并到位置 $j$ 所能得到的最大得分, $sum_i$ 为前 $i$ 堆石子数的前缀和。 -写出 ** 状态转移方程 ** : $f_{i,j}=\max\{f_{i,k}+f_{k+1,j}+(sum_j-sum_i)\}(i\le k\le j)$ +写出**状态转移方程**: $f_{i,j}=\max\{f_{i,k}+f_{k+1,j}+(sum_j-sum_i)\}(i\le k\le j)$ -考虑常规的转移方法,枚举 $i$、$j$ 和 $k$,时间复杂度为 $O(n^3)$。 +考虑常规的转移方法,枚举 $i$ 、 $j$ 和 $k$ ,时间复杂度为 $O(n^3)$ 。 ### 什么是四边形不等式? -对于 $a= 1; i--) { 计算 $f_{i,j}$ 时,我们要循环 $idx_{i+1,j}-idx_{i,j-1}$ 次,那么一共加起来会循环多少次呢? -因为 $\sum_{i=1}^{n-1}(idx_{i+1,i+1}-idx_{i,i})=idx_{n,n}-idx_{1,1}$ 很显然和 $n$ 同阶,那么它的 $n$ 倍就和 $n^2$ 同阶,时间复杂度是 $O(n^2)$。 +因为 $\sum_{i=1}^{n-1}(idx_{i+1,i+1}-idx_{i,i})=idx_{n,n}-idx_{1,1}$ 很显然和 $n$ 同阶,那么它的 $n$ 倍就和 $n^2$ 同阶,时间复杂度是 $O(n^2)$ 。 ### 一道练习题 -[洛谷 P4767 \[IOI2000\] 邮局](https://www.luogu.org/problemnew/show/P4767) +[洛谷 P4767\[IOI2000\]邮局](https://www.luogu.org/problemnew/show/P4767) ### 参考资料 diff --git a/docs/dp/state.md b/docs/dp/state.md index 267e4ae9..850d3088 100644 --- a/docs/dp/state.md +++ b/docs/dp/state.md @@ -1,6 +1,6 @@ 学习状压 dp 之前,请确认你已经完成了[动态规划初步](/dp/)部分内容的学习 -(建议学习[位运算](/math/bit/)部分的内容) +(建议学习[位运算](/math/bit/)部分的内容) ### 状压 DP 简介 @@ -28,11 +28,11 @@ for (int i = 1; i <= top; i++) ans += dp[上界][Type[i]]; #### 典型例题 -[\[USACO06NOV\] 玉米田 Corn Fields](https://www.luogu.org/problemnew/show/P1879) +[\[USACO06NOV\]玉米田 Corn Fields](https://www.luogu.org/problemnew/show/P1879) 显然,这是一道典型的动态规划题目,但由于方案数过多,应使用状压 dp 避免超时 -本题所 "压缩" 的是 "每行可行的状态" 和 "每行土地的状态", 而储存答案的 dp 数组就应同时体现这两个特点 (所以本题 dp 数组为二维) +本题所 "压缩" 的是 "每行可行的状态" 和 "每行土地的状态", 而储存答案的 dp 数组就应同时体现这两个特点(所以本题 dp 数组为二维) 具体实现方法同上方伪代码 diff --git a/docs/dp/tree.md b/docs/dp/tree.md index c1d6e33c..0d62f49c 100644 --- a/docs/dp/tree.md +++ b/docs/dp/tree.md @@ -6,15 +6,15 @@ 以下面这道题为例,介绍一下树形 DP 的一般过程。 -??? note " 例题 [洛谷 P1352 没有上司的舞会](https://www.luogu.org/problemnew/show/P1352)" - 某大学有 $n$ 个职员,编号为 $1\text{~} N$ 。他们之间有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子结点的直接上司。现在有个周年庆宴会,宴会每邀请来一个职员都会增加一定的快乐指数 $a_i$,但是呢,如果某个职员的上司来参加舞会了,那么这个职员就无论如何也不肯来参加舞会了。所以,请你编程计算,邀请哪些职员可以使快乐指数最大,求最大的快乐指数。 +??? note " 例题[洛谷 P1352 没有上司的舞会](https://www.luogu.org/problemnew/show/P1352)" + 某大学有 $n$ 个职员,编号为 $1\text{~} N$ 。他们之间有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子结点的直接上司。现在有个周年庆宴会,宴会每邀请来一个职员都会增加一定的快乐指数 $a_i$ ,但是呢,如果某个职员的上司来参加舞会了,那么这个职员就无论如何也不肯来参加舞会了。所以,请你编程计算,邀请哪些职员可以使快乐指数最大,求最大的快乐指数。 我们可以定义 $f(i,0/1)$ 代表以 $i$ 为根的子树的最优解(第二维的值为 0 代表 $i$ 不参加舞会的情况,1 代表 $i$ 参加舞会的情况)。 显然,我们可以推出下面两个状态转移方程(其中下面的 $x$ 都是 $i$ 的儿子): -- $f(i,0) = \sum\max \{f(x,1),f(x,0)\}$ (上司不参加舞会时,下属可以参加,也可以不参加) -- $f(i,1) = \sum{f(x,0)} + a_i$ (上司参加舞会时,下属都不会参加) +- $f(i,0) = \sum\max \{f(x,1),f(x,0)\}$ (上司不参加舞会时,下属可以参加,也可以不参加) +- $f(i,1) = \sum{f(x,0)} + a_i$ (上司参加舞会时,下属都不会参加) 我们可以通过 DFS,在返回上一层时更新当前节点的最优解。 diff --git a/docs/ds/balanced-in-seg.md b/docs/ds/balanced-in-seg.md index d5162853..2e2a030c 100644 --- a/docs/ds/balanced-in-seg.md +++ b/docs/ds/balanced-in-seg.md @@ -8,7 +8,7 @@ 关于树套树的构建,我们对于外层线段树正常建树,对于线段树上的某一个节点,建立一棵平衡树,包含该节点所覆盖的序列。具体操作时我们可以将序列元素一个个插入,每经过一个线段树节点,就将该元素加入到该节点的平衡树中。 -操作一,求某区间中某值的排名:我们对于外层线段树正常操作,对于在某区间中的节点的平衡树,我们返回平衡树中比该值小的元素个数,合并区间时,我们将小的元素个数求和即可。最后将返回值 $+1$,即为某值在某区间中的排名。 +操作一,求某区间中某值的排名:我们对于外层线段树正常操作,对于在某区间中的节点的平衡树,我们返回平衡树中比该值小的元素个数,合并区间时,我们将小的元素个数求和即可。最后将返回值 $+1$ ,即为某值在某区间中的排名。 操作二,求某区间中排名为 $k$ 的值:我们可以采用二分策略。因为一个元素可能存在多个,其排名为一区间,且有些元素原序列不存在。所以我们采取和操作一类似的思路,我们用小于该值的元素个数作为参考进行二分,即可得解。 @@ -18,16 +18,16 @@ ## 空间复杂度 -我们每个元素加入 $\log n$ 个平衡树,所以空间复杂度为 $(n + q)\log{n}$。 +我们每个元素加入 $\log n$ 个平衡树,所以空间复杂度为 $(n + q)\log{n}$ 。 ## 时间复杂度 -对于 $1,2,4$ 操作,我们考虑我们在外层线段树上进行 $\log{n}$ 次操作,每次操作会在一个内层平衡树树上进行 $\log{n}$ 次操作,所以时间复杂度为 $\log^2{n}$。 -对于 $3$ 操作,多一个二分过程,为 $\log^3{n}$。 +对于 $1,2,4$ 操作,我们考虑我们在外层线段树上进行 $\log{n}$ 次操作,每次操作会在一个内层平衡树树上进行 $\log{n}$ 次操作,所以时间复杂度为 $\log^2{n}$ 。 +对于 $3$ 操作,多一个二分过程,为 $\log^3{n}$ 。 ## 经典例题 -[二逼平衡树](https://www.lydsy.com/JudgeOnline/problem.php?id=3196) 外层线段树,内层平衡树。 +[二逼平衡树](https://www.lydsy.com/JudgeOnline/problem.php?id=3196)外层线段树,内层平衡树。 ## 示例代码 @@ -92,4 +92,4 @@ int vec_front(int k, int l, int r, int x, int y, int t) { ## 相关算法 -面对多维度信息的题目时,如果题目没有要求强制在线,我们还可以考虑 **CDQ 分治**,或者**整体二分**等分治算法,来避免使用高级数据结构,减少代码实现难度。 +面对多维度信息的题目时,如果题目没有要求强制在线,我们还可以考虑**CDQ 分治**,或者**整体二分**等分治算法,来避免使用高级数据结构,减少代码实现难度。 diff --git a/docs/ds/bit.md b/docs/ds/bit.md index c73af8fd..3dede5e9 100644 --- a/docs/ds/bit.md +++ b/docs/ds/bit.md @@ -20,25 +20,25 @@ 最下面的八个方块就代表存入 $a$ 中的八个数,现在都是十进制。 -他们上面的参差不齐的剩下的方块就代表 $a$ 的上级——$c$ 数组。 +他们上面的参差不齐的剩下的方块就代表 $a$ 的上级—— $c$ 数组。 很显然看出: -$c[2]$ 管理的是 $a[1]$ & $a[2]$ ; -$c[4]$ 管理的是 $a[1]$ & $a[2]$ & $a[3]$ & $a[4]$ ; -$c[6]$ 管理的是 $a[5]$ & $a[6]$ ;$c[8]$ 则管理全部 $8$ 个数。 + $c[2]$ 管理的是 $a[1]$ & $a[2]$ ; + $c[4]$ 管理的是 $a[1]$ & $a[2]$ & $a[3]$ & $a[4]$ ; + $c[6]$ 管理的是 $a[5]$ & $a[6]$ ; $c[8]$ 则管理全部 $8$ 个数。 所以,如果你要算区间和的话,比如说要算 $a[51]$ ~ $a[91]$ 的区间和,暴力算当然可以,那上百万的数,那就 RE 喽。 那么这种类似于跳一跳的连续跳到中心点而分值不断变大的原理是一样的(倍增)。 -你从 $91$ 开始往前跳,发现 $c[n]$($n$ 我也不确定是多少,算起来太麻烦,就意思一下)只管 $a[91]$ 这个点,那么你就会找 $a[90]$,发现 $c[n - 1]$ 管的是 $a[90]$ & $a[89]$ ;那么你就会直接跳到 $a[88]$ ,$c[n - 2]$ 就会管 $a[81]$ ~ $a[88]$ 这些数,下次查询从 $a[80]$ 往前找,以此类推。 +你从 $91$ 开始往前跳,发现 $c[n]$ ( $n$ 我也不确定是多少,算起来太麻烦,就意思一下)只管 $a[91]$ 这个点,那么你就会找 $a[90]$ ,发现 $c[n - 1]$ 管的是 $a[90]$ & $a[89]$ ;那么你就会直接跳到 $a[88]$ , $c[n - 2]$ 就会管 $a[81]$ ~ $a[88]$ 这些数,下次查询从 $a[80]$ 往前找,以此类推。 * * * ## 用法及操作 -那么问题来了,你是怎么知道 $c$ 管的 $a$ 的个数分别是多少呢?你那个 $1$ 个,$2$ 个,$8$ 个…… 是怎么来的呢? -这时,我们引入一个函数—— `lowbit`: +那么问题来了,你是怎么知道 $c$ 管的 $a$ 的个数分别是多少呢?你那个 $1$ 个, $2$ 个, $8$ 个……是怎么来的呢? +这时,我们引入一个函数—— `lowbit` : ```cpp int lowbit(int x) { @@ -47,22 +47,22 @@ int lowbit(int x) { } ``` -`lowbit` 的意思注释说明了,咱们就用这个说法来证明一下 $a[88]$: -$88_{(10)}=1011000_{(2)}$ -发现第一个 $1$ 以及他后面的 $0$ 组成的二进制是 $1000$ -$1000_{(2)} = 8_{(10)}$ -$1000$ 对应的十进制是 $8$,所以 $c$ 一共管理 $8$ 个 $a$。 + `lowbit` 的意思注释说明了,咱们就用这个说法来证明一下 $a[88]$ : + $88_{(10)}=1011000_{(2)}$ +发现第一个 $1$ 以及他后面的 $0$ 组成的二进制是 $1000$ + $1000_{(2)} = 8_{(10)}$ + $1000$ 对应的十进制是 $8$ ,所以 $c$ 一共管理 $8$ 个 $a$ 。 这就是 `lowbit` 的用处,仅此而已(但也相当有用)。 **你可能又问了:x & -x 是什么意思啊?** -> $-x$ 代表 $x$ 的负数,计算机中负数使用对应的正数的补码来表示。 +> $-x$ 代表 $x$ 的负数,计算机中负数使用对应的正数的补码来表示。 例如 : -$x =88_{(10)}=1011000_{(2)}$; -$-x = -88_{(10)} = (0100111_{(2)} + 1_{(2)}) =101000_{(2)}$; -$x\ \& \ (-x) = 1000_{(2)} = 8_{(10)}$。 + $x =88_{(10)}=1011000_{(2)}$ ; + $-x = -88_{(10)} = (0100111_{(2)} + 1_{(2)}) =101000_{(2)}$ ; + $x\ \& \ (-x) = 1000_{(2)} = 8_{(10)}$ 。 神奇吧,我也觉得神奇! diff --git a/docs/ds/block-list.md b/docs/ds/block-list.md index 38a95369..b900d006 100644 --- a/docs/ds/block-list.md +++ b/docs/ds/block-list.md @@ -5,7 +5,7 @@ 不难发现块状链表就是一个链表,每个节点指向一个数组。 我们把原来长度为 n 的数组分为 $\sqrt{n}$ 个节点,每个节点对应的数组大小为 $\sqrt{n}$ 。 所以我们这么定义结构体,代码见下。 -其中 `sqn` 表示 `sqrt(n)` 即 $\sqrt{n}$,`pb` 表示 `push_back`,即在这个 `node` 中加入一个元素。 +其中 `sqn` 表示 `sqrt(n)` 即 $\sqrt{n}$ , `pb` 表示 `push_back` ,即在这个 `node` 中加入一个元素。 ```cpp struct node { @@ -18,16 +18,16 @@ struct node { ``` 块状链表应该至少支持:分裂、插入、查找。 -什么是分裂?分裂就是分裂一个 `node`,变成两个小的 `node`,以保证每个 `node` 的大小都接近 $\sqrt{n}$ (否则可能退化成普通数组)。当一个 `node` 的大小超过 $2\times \sqrt{n}$ 时执行分裂操作。 +什么是分裂?分裂就是分裂一个 `node` ,变成两个小的 `node` ,以保证每个 `node` 的大小都接近 $\sqrt{n}$ (否则可能退化成普通数组)。当一个 `node` 的大小超过 $2\times \sqrt{n}$ 时执行分裂操作。 -分裂操作怎么做呢?先新建一个节点,再把被分裂的节点的后 $\sqrt{n}$ 个值 `copy` 到新节点,然后把被分裂的节点的后 $\sqrt{n}$ 个值删掉(`size--`),最后把新节点插入到被分裂节点的后面即可。 +分裂操作怎么做呢?先新建一个节点,再把被分裂的节点的后 $\sqrt{n}$ 个值 `copy` 到新节点,然后把被分裂的节点的后 $\sqrt{n}$ 个值删掉( `size--` ),最后把新节点插入到被分裂节点的后面即可。 块状链表的所有操作的复杂度都是 $\sqrt{n}$ 的。 还有一个要说的。 -随着元素的插入(或删除),$n$ 会变, $\sqrt{n}$ 也会变。这样块的大小就会变化,我们难道还要每次维护块的大小? +随着元素的插入(或删除), $n$ 会变, $\sqrt{n}$ 也会变。这样块的大小就会变化,我们难道还要每次维护块的大小? -其实不然,把 $\sqrt{n}$ 设置为一个定值即可。比如题目给的范围是 $10^6$,那么 $\sqrt{n}$ 就设置为大小为 $10^3$ 的常量,不用更改它。 +其实不然,把 $\sqrt{n}$ 设置为一个定值即可。比如题目给的范围是 $10^6$ ,那么 $\sqrt{n}$ 就设置为大小为 $10^3$ 的常量,不用更改它。 ```cpp list > orz_list; diff --git a/docs/ds/bst.md b/docs/ds/bst.md index e92e562e..b15bde79 100644 --- a/docs/ds/bst.md +++ b/docs/ds/bst.md @@ -30,7 +30,7 @@ void print(int o) //遍历以 o 为根节点的二叉搜索树 } ``` -### 查找最小 / 最大值 +### 查找最小/最大值 由二叉搜索树的性质可得,二叉搜索树上的最小值为二叉搜索树左链的顶点,最大值为二叉搜索树右链的顶点。时间复杂度为 $O(h)$ 。 diff --git a/docs/ds/dividing.md b/docs/ds/dividing.md index b6c95321..c3267973 100644 --- a/docs/ds/dividing.md +++ b/docs/ds/dividing.md @@ -1,17 +1,16 @@ -划分树是一种来解决区间第 $K$ 大的一种数据结构, 其常数、理解难度都要比主席树低很多。同时, 划分树紧贴 “第 $K$ 大”,所以是一种基于排序的一种数据结构。 +划分树是一种来解决区间第 $K$ 大的一种数据结构,其常数、理解难度都要比主席树低很多。同时,划分树紧贴“第 $K$ 大”,所以是一种基于排序的一种数据结构。 **建议先学完主席树再看划分树哦** ## 建树 -划分树的建树比较简单, 但是相对于其他树来说比较复杂。 -![](./images/dividing1.png) +划分树的建树比较简单,但是相对于其他树来说比较复杂。![](./images/dividing1.png) -如图, 每一层都有一个看似无序的数组。其实, 每一个被红色标记的数字都是**要分配到左儿子的**。而分配的规则是什么? 就是与**这一层的中位数**做比较, $\leq$ 左边, 否则右边。但是这里要注意一下: 并不是严格的 $\leq$ **左边, 否则右边**。因为中位数可能有相同, 而且与 $N$ 的奇偶有一定关系。下面的代码展示会有一个巧妙的运用, 大家可以参照代码。 +如图,每一层都有一个看似无序的数组。其实,每一个被红色标记的数字都是**要分配到左儿子的**。而分配的规则是什么?就是与**这一层的中位数**做比较, $\leq$ 左边,否则右边。但是这里要注意一下:并不是严格的 $\leq$ **左边,否则右边**。因为中位数可能有相同,而且与 $N$ 的奇偶有一定关系。下面的代码展示会有一个巧妙的运用,大家可以参照代码。 -我们不肯能每一次都对每一层排序, 这样子不说常数, 就算是理论复杂度也过不去。我们想, 找中位数, 一次排序就够了。为什么? 比如, 我们求 $l,r$ 的中位数, 其实就是在排完序过后的 $num[mid]$。 +我们不肯能每一次都对每一层排序,这样子不说常数,就算是理论复杂度也过不去。我们想,找中位数,一次排序就够了。为什么?比如,我们求 $l,r$ 的中位数,其实就是在排完序过后的 $num[mid]$ 。 -两个关键数组: +两个关键数组: ```text tree[log(N),N] : 也就是树,要存下所有的值,空间复杂度 $O(n\log n)$。 @@ -54,11 +53,11 @@ end; ## 查询 -那我们先扯一下主席树的内容。在用主席树求区间第 $K$ 小的时候, 我们以 $K$ 为基准, 向左就向左, 向右要减去向左的值, 在划分树中也是这样子的。 +那我们先扯一下主席树的内容。在用主席树求区间第 $K$ 小的时候,我们以 $K$ 为基准,向左就向左,向右要减去向左的值,在划分树中也是这样子的。 -查询难理解的, 在于 **区间缩小** 这种东西。下图, 我查询的是 $3$ 到 $7$, 那么下一层我就只需要查询 $2$ 到 $3$ 了。当然, 我们定义 $left,right$ 为缩小后的区间 (目标区间), $l,r$ 还是我所在节点的区间。那为什么要标出目标区间呢? 因为那是**判定答案在左边, 右边的基准**。 +查询难理解的,在于**区间缩小**这种东西。下图,我查询的是 $3$ 到 $7$ , 那么下一层我就只需要查询 $2$ 到 $3$ 了。当然,我们定义 $left,right$ 为缩小后的区间(目标区间), $l,r$ 还是我所在节点的区间。那为什么要标出目标区间呢?因为那是**判定答案在左边,右边的基准**。 - ![](./images/dividing2.png) +![](./images/dividing2.png) ```pascal function Query(left,right,k,l,r,deep:longint):longint; @@ -81,12 +80,12 @@ end; ## 理论复杂度和亲测结果 -时间复杂度 : 一次查询只需要 $O(\log n)$,$m$次询问, 就是 $O(m\log n)$。 +时间复杂度 : 一次查询只需要 $O(\log n)$ , $m$ 次询问,就是 $O(m\log n)$ 。 空间复杂度 : 只需要存储 $O(n\log n)$ 个数字。 -亲测结果: 主席树 : $1482 \text{ms}$、划分树 : $889 \text{ms}$。 (非递归, 常数比较小) +亲测结果:主席树 : $1482 \text{ms}$ 、划分树 : $889 \text{ms}$ 。(非递归,常数比较小) ## 后记 -大家可以试着去写非递归版哦。参考博文 : [传送门](https://blog.csdn.net/littlewhite520/article/details/70250722)。 +大家可以试着去写非递归版哦。参考博文 :[传送门](https://blog.csdn.net/littlewhite520/article/details/70250722)。 diff --git a/docs/ds/dsu.md b/docs/ds/dsu.md index 64e91697..a92e2889 100644 --- a/docs/ds/dsu.md +++ b/docs/ds/dsu.md @@ -1,7 +1,7 @@ 并查集是一种树形的数据结构,顾名思义,它用于处理一些不交集的**合并**及**查询**问题。 它支持两种操作: -- 查找 (Find):确定某个元素处于哪个子集; +- 查找(Find):确定某个元素处于哪个子集; - 合并(Union):将两个子集合并成一个集合。 @@ -19,7 +19,7 @@ void makeSet(int size) { ## 查找 !!! 举个例子 -几个家族进行宴会,但是家族普遍长寿,所以人数众多。由于长时间的分离以及年龄的增长,这些人逐渐忘掉了自己的亲人,只记得自己的爸爸是谁了,而最长者(称为「祖先」)的父亲已经去世,他只知道自己是祖先。为了确定自己是哪个家族,他们想出了一个办法,只要问自己的爸爸是不是祖先,一层一层的向上问,直到问到祖先。如果要判断两人是否在同一家族,只要看两人的祖先是不是同一人就可以了。 +几个家族进行宴会,但是家族普遍长寿,所以人数众多。由于长时间的分离以及年龄的增长,这些人逐渐忘掉了自己的亲人,只记得自己的爸爸是谁了,而最长者(称为「祖先」)的父亲已经去世,他只知道自己是祖先。为了确定自己是哪个家族,他们想出了一个办法,只要问自己的爸爸是不是祖先,一层一层的向上问,直到问到祖先。如果要判断两人是否在同一家族,只要看两人的祖先是不是同一人就可以了。 在这样的思想下,并查集的查找算法诞生了。我们可以用代码模拟这个过程。 @@ -38,7 +38,7 @@ int find(int x) //寻找x的祖先 ### 路径压缩 -这样的确可以达成目的,但是显然效率实在太低。为什么呢?因为我们使用了太多没用的信息,我关心的是我祖先是谁,我爸爸是谁没什么关系,这样一层一层找太浪费时间,不如我直接当祖先的儿子,问一次就可以出结果了。甚至祖先是谁都无所谓,只要这个人可以代表我们家族就能得到想要的效果。**把在路径上的每个节点都直接连接到根上**,这就是路径压缩。 +这样的确可以达成目的,但是显然效率实在太低。为什么呢?因为我们使用了太多没用的信息,我关心的是我祖先是谁,我爸爸是谁没什么关系,这样一层一层找太浪费时间,不如我直接当祖先的儿子,问一次就可以出结果了。甚至祖先是谁都无所谓,只要这个人可以代表我们家族就能得到想要的效果。**把在路径上的每个节点都直接连接到根上**,这就是路径压缩。 于是用代码实现它。 ```cpp @@ -51,7 +51,7 @@ int find(int x) { ## 合并 -宴会上,一个家族的祖先突然对另一个家族说: 我们两个家族交情这么好,不如合成一家好了。另一个家族也欣然接受了。 +宴会上,一个家族的祖先突然对另一个家族说:我们两个家族交情这么好,不如合成一家好了。另一个家族也欣然接受了。 我们之前说过,并不在意祖先究竟是谁,所以只要其中一个祖先变成另一个祖先的儿子就可以了。 ```cpp @@ -67,7 +67,7 @@ void unionSet(int x, int y) // x与y所在家族合并 ### 启发式合并(按秩合并) -一个祖先突然抖了个机灵:「你们家族人比较少,搬家到我们家族里比较方便,我们要是搬过去的话太费事了。」 +一个祖先突然抖了个机灵:「你们家族人比较少,搬家到我们家族里比较方便,我们要是搬过去的话太费事了。」 启发式合并是将深度小的集合合并到深度大的集合(也称为**按秩合并**),但是笔者认为路径压缩之后它就失去意义了,或者不如按照节点数量合并,这样还可以减少下次路径压缩的工作量。(反正启发式合并用得很少,路径压缩已经够快了。) @@ -87,22 +87,22 @@ void unionSet(int x, int y) { ### 时间复杂度 -同时使用路径压缩和启发式合并之后,并查集的每个操作平均时间仅为 $O(\alpha(n))$ ,其中 $\alpha$ 为 [阿克曼函数](https://en.wikipedia.org/wiki/Ackermann_function) 的反函数,其增长极其缓慢,也就是说其平均运行时间可以认为是一个很小的常数。 +同时使用路径压缩和启发式合并之后,并查集的每个操作平均时间仅为 $O(\alpha(n))$ ,其中 $\alpha$ 为[阿克曼函数](https://en.wikipedia.org/wiki/Ackermann_function)的反函数,其增长极其缓慢,也就是说其平均运行时间可以认为是一个很小的常数。 ### 空间复杂度 -显然为 $O(n)$。 +显然为 $O(n)$ 。 ## 经典题目 -[\[NOI2015\] 程序自动分析](https://www.lydsy.com/JudgeOnline/problem.php?id=4195) +[\[NOI2015\]程序自动分析](https://www.lydsy.com/JudgeOnline/problem.php?id=4195) -[\[JSOI2008\] 星球大战](https://www.lydsy.com/JudgeOnline/problem.php?id=1015) +[\[JSOI2008\]星球大战](https://www.lydsy.com/JudgeOnline/problem.php?id=1015) -[\[NOI2001\] 食物链](https://www.luogu.org/problemnew/show/P2024) +[\[NOI2001\]食物链](https://www.luogu.org/problemnew/show/P2024) -[\[NOI2002\] 银河英雄传说](https://www.luogu.org/problemnew/show/P1196) +[\[NOI2002\]银河英雄传说](https://www.luogu.org/problemnew/show/P1196) ## 其他应用 -[最小生成树](/graph/mst) Kruskal 是基于并查集的算法。 +[最小生成树](/graph/mst)Kruskal 是基于并查集的算法。 diff --git a/docs/ds/hash.md b/docs/ds/hash.md index d06305c0..0155eb15 100644 --- a/docs/ds/hash.md +++ b/docs/ds/hash.md @@ -4,11 +4,11 @@ ## 哈希函数 -要让 key 对应到内存中的位置,就要为 key 计算索引,也就是计算这个数据应该放到哪里。这个根据 key 计算索引的函数就叫做哈希函数,也称散列函数。举个例子,比如 key 是一个人的身份证号码,哈希函数就可以是号码的后四位,当然也可以是号码的前四位。生活中常用的 “手机尾号” 也是一种哈希函数。在实际的应用中,key 可能是更复杂的东西,比如浮点数、字符串、结构体等,这时候就要根据具体情况设计合适的哈希函数。哈希函数应当易于计算,并且尽量使计算出来的索引均匀分布。 +要让 key 对应到内存中的位置,就要为 key 计算索引,也就是计算这个数据应该放到哪里。这个根据 key 计算索引的函数就叫做哈希函数,也称散列函数。举个例子,比如 key 是一个人的身份证号码,哈希函数就可以是号码的后四位,当然也可以是号码的前四位。生活中常用的“手机尾号”也是一种哈希函数。在实际的应用中,key 可能是更复杂的东西,比如浮点数、字符串、结构体等,这时候就要根据具体情况设计合适的哈希函数。哈希函数应当易于计算,并且尽量使计算出来的索引均匀分布。 在 OI 中,最常见的情况应该是 key 为整数的情况。当 key 的范围比较小的时候,可以直接把 key 作为数组的下标,但当 key 的范围比较大,比如以 10^9 范围内的整数作为 key 的时候,就需要用到哈希表。一般把 key 模一个较大的质数作为索引,也就是取 $f(x)=x \mod M$ 作为哈希函数。另一种比较常见的情况是 key 为字符串的情况,在 OI 中,一般不直接把字符串作为 key,而是先算出字符串的哈希值,再把其哈希值作为 key 插入到哈希表里。 -能为 key 计算索引之后,我们就可以知道每个 value 应该放在哪里了。假设我们用数组 a 存放数据,哈希函数是 f,那键值对 (key,value) 就应该放在 a[f(key)] 上。不论 key 是什么类型,范围有多大,f(key) 都是在可接受范围内的整数,可以作为数组的下标。 +能为 key 计算索引之后,我们就可以知道每个 value 应该放在哪里了。假设我们用数组 a 存放数据,哈希函数是 f,那键值对 (key,value) 就应该放在 a[f(key)]上。不论 key 是什么类型,范围有多大,f(key) 都是在可接受范围内的整数,可以作为数组的下标。 ## 冲突 @@ -16,7 +16,7 @@ ### 拉链法 -拉链法是在每个存放数据的地方开一个链表,如果有多个 key 索引到同一个地方,只用把他们都放到那个位置的链表里就行了。查询的时候需要把对应位置的链表整个扫一遍,对其中的每个数据比较其 key 与查询的 key 是否一致。如果索引的范围是 1~M ,哈希表的大小为 N ,那么一次插入 / 查询需要进行期望 $O(\frac{N}{M})$ 次比较。 +拉链法是在每个存放数据的地方开一个链表,如果有多个 key 索引到同一个地方,只用把他们都放到那个位置的链表里就行了。查询的时候需要把对应位置的链表整个扫一遍,对其中的每个数据比较其 key 与查询的 key 是否一致。如果索引的范围是 1~M,哈希表的大小为 N,那么一次插入/查询需要进行期望 $O(\frac{N}{M})$ 次比较。 ## 实现 @@ -51,4 +51,4 @@ struct HashTable { ## 例题 -[\[JLOI2011\] 不重复数字](https://www.lydsy.com/JudgeOnline/problem.php?id=2761) +[\[JLOI2011\]不重复数字](https://www.lydsy.com/JudgeOnline/problem.php?id=2761) diff --git a/docs/ds/heap.md b/docs/ds/heap.md index 4d9381d6..70649a26 100644 --- a/docs/ds/heap.md +++ b/docs/ds/heap.md @@ -16,15 +16,15 @@ 在 NOIP 中,我们只要求一个能支持主要操作的堆就行,也就是二叉堆。 -- 二叉堆 _(binary heap)_ +- 二叉堆_(binary heap)_ 最基础的堆,不支持 merge 和可持久化,所有操作的复杂度都是 $O(\log n)$ 的。 -- 二项堆 _(binomial heap)_ +- 二项堆_(binomial heap)_ -支持 merge 的堆,(也能可持久化),所有操作的复杂度都是 $O(\log n)$。 +支持 merge 的堆,(也能可持久化),所有操作的复杂度都是 $O(\log n)$ 。 -- Fib 堆 _(Fibonacci heap)_ +- Fib 堆_(Fibonacci heap)_ 除了不能可持久化,支持全部功能,而且除了 deletemin 以外都是均摊 $O(1)$ 的。 @@ -34,9 +34,9 @@ 从二叉堆的结构说起,它是一棵二叉树,并且是完全二叉树,每个结点中存有一个元素(或者说,有个权值)。 -堆性质:父亲的权值不大于儿子的权值 (小根堆)。 +堆性质:父亲的权值不大于儿子的权值(小根堆)。 -由堆性质,树根存的是最小值 (getmin 操作就解决了)。 +由堆性质,树根存的是最小值(getmin 操作就解决了)。 ### 插入操作 @@ -70,11 +70,11 @@ 可以证明,删除并向下调整后,没有其他结点不满足堆性质。 -时间复杂度 $O(\log n)$。 +时间复杂度 $O(\log n)$ 。 ### 减小某个点的权值 -很显然,直接修改后,向上调整一次即可,时间复杂度为 $O(\log n)$。 +很显然,直接修改后,向上调整一次即可,时间复杂度为 $O(\log n)$ 。 ### 实现 @@ -108,7 +108,7 @@ down(x) { #### 方法一:使用 decreasekey(即,向上调整) -从根开始,按 BFS 序进行. +从根开始,按 BFS 序进行。 ```text build_heap_1() { @@ -116,9 +116,9 @@ build_heap_1() { } ``` -为啥这么做:对于第 $k$ 层的结点,向上调整的复杂度为 $O(k)$ 而不是 $O(\log n)$。 +为啥这么做:对于第 $k$ 层的结点,向上调整的复杂度为 $O(k)$ 而不是 $O(\log n)$ 。 -总复杂度:$\log 1 + \log 2 + \cdots + \log n = \Theta(n \log n)$。 +总复杂度: $\log 1 + \log 2 + \cdots + \log n = \Theta(n \log n)$ 。 (在「基于比较的排序」中证明过) @@ -134,7 +134,7 @@ build_heap_2() { 换一种理解方法,每次「合并」两个已经调整好的堆,这说明了正确性。 -注意到向下调整的复杂度,为 $O(\log n - k)$。 +注意到向下调整的复杂度,为 $O(\log n - k)$ 。 $$ \begin{aligned} diff --git a/docs/ds/lct.md b/docs/ds/lct.md index 0e0b57b9..2df3dff3 100644 --- a/docs/ds/lct.md +++ b/docs/ds/lct.md @@ -1,85 +1,85 @@ ## 简介 -1. Link/Cut Tree 是一种数据结构, 我们用它来解决 动态树问题 +1. Link/Cut Tree 是一种数据结构,我们用它来解决动态树问题 2. Link/Cut Tree 又称 Link-Cut Tree,简称 LCT, 但它不叫动态树,动态树是指一类问题。 -3. Splay Tree 是 LCT 的基础, 但是 LCT ⽤的 Splay Tree 和普通的 Splay 在细节处不太一样。 +3. Splay Tree 是 LCT 的基础,但是 LCT ⽤的 Splay Tree 和普通的 Splay 在细节处不太一样。 4. 这是⼀个和 Splay ⼀样只需要写⼏ (yi) 个 (dui) 核心函数就能实现一切的数据结构。 ## 问题引入 -- 维护一棵树, 支持如下操作。 +- 维护一棵树,支持如下操作。 - 修改两点间路径权值。 - 查询两点间路径权值和。 - 修改某点子树权值。 - 查询某点子树权值和。 - 唔, 看上去是一道树剖模版题。 + 唔,看上去是一道树剖模版题。 那么我们加两个操作 -- 断开并连接⼀一些边, 保证仍是⼀一棵树。 +- 断开并连接⼀一些边,保证仍是⼀一棵树。 - 在线求出上⾯面的答案。 ——动态树问题的解决方法:Link/Cut Tree! ## 动态树问题 -- 维护一个 森林, 支持删除某条边, 加⼊某条边, 并保证加边, 删边之后仍是森林。我们要维护这个森林的一些信息。 +- 维护一个森林, 支持删除某条边,加⼊某条边,并保证加边,删边之后仍是森林。我们要维护这个森林的一些信息。 -- 一般的操作有两点连通性, 两点路径权值和, 连接两点和切断某条边、修改信息等。 +- 一般的操作有两点连通性,两点路径权值和,连接两点和切断某条边、修改信息等。 * * * ### 从 LCT 的角度回顾一下树链剖分 -- 对整棵树按子树⼤小进⾏剖分, 并重新标号。 +- 对整棵树按子树⼤小进⾏剖分,并重新标号。 -- 我们发现重新标号之后, 在树上形成了一些以链为单位的连续区间, 并且可以用线段树进⾏区间操作。 +- 我们发现重新标号之后,在树上形成了一些以链为单位的连续区间,并且可以用线段树进⾏区间操作。 ### 转向动态树问题 - 我们发现我们刚刚讲的树剖是以子树⼤小作为划分条件。 -- 那我们能不能重定义一种剖分, 使它更适应我们的动态树问题呢? +- 那我们能不能重定义一种剖分,使它更适应我们的动态树问题呢? - 考虑动态树问题需要什么链。 -- 由于动态维护⼀个森林, 显然我们希望这个链是我们指定的链, 以便利⽤来求解。 +- 由于动态维护⼀个森林,显然我们希望这个链是我们指定的链,以便利⽤来求解。 ## 实链剖分 -- 对于⼀个点连向它所有⼉子的边 , 我们⾃己选择⼀条边进行剖分, 我们称被选择的边为实边, 其他边则为虚边。 +- 对于⼀个点连向它所有⼉子的边 , 我们⾃己选择⼀条边进行剖分,我们称被选择的边为实边,其他边则为虚边。 -- 对于实边, 我们称它所连接的⼉子为实⼉子。 -- 对于⼀条由实边组成的链, 我们同样称之为实链。 +- 对于实边,我们称它所连接的⼉子为实⼉子。 +- 对于⼀条由实边组成的链,我们同样称之为实链。 -- 请记住我们选择实链剖分的最重要的原因: 它是我们选择的, 灵活且可变。 +- 请记住我们选择实链剖分的最重要的原因:它是我们选择的,灵活且可变。 -- 正是它的这种灵活可变性, 我们采用 Splay Tree 来维护这些实链。 +- 正是它的这种灵活可变性,我们采用 Splay Tree 来维护这些实链。 ## LCT! -- 我们可以简单的把 LCT 理解成用⼀些 Splay 来维护动态的树链剖分, 以期实现动态树上的区间操作。 +- 我们可以简单的把 LCT 理解成用⼀些 Splay 来维护动态的树链剖分,以期实现动态树上的区间操作。 -- 对于每条实链, 我们建⼀个 Splay 来维护整个链区间的信息。 -- 接下来, 我们来学习 LCT 的具体结构。 +- 对于每条实链,我们建⼀个 Splay 来维护整个链区间的信息。 +- 接下来,我们来学习 LCT 的具体结构。 ## - 辅助树 -- 我们刚才在说的建树方法, 其实就是辅助树的建树方法, 我们先来 看⼀看辅助树的一些性质, 再通过一张图实际了解一下辅助树的具体结构。 +- 我们刚才在说的建树方法,其实就是辅助树的建树方法,我们先来 看⼀看辅助树的一些性质,再通过一张图实际了解一下辅助树的具体结构。 -1. 每⼀个 Splay 维护的是一条路径, 并且在原树中所有节点深度严格递增, 并且, 中序遍历这棵 Splay 得到的点序列列的点深度严格递增。 +1. 每⼀个 Splay 维护的是一条路径,并且在原树中所有节点深度严格递增,并且,中序遍历这棵 Splay 得到的点序列列的点深度严格递增。 2. 每个节点包含且仅包含于一棵 Splay 中。 -3. ⼀棵 Splay 的根节点的 Father 指向它在辅助树中的父亲结点。但是它父亲结点的 ch 并没有指向这个点的。即父亲不不⼀定认⼉子, ⽽⼉子能找到⽗亲。 -4. 由于 LCT 的 Access 操作(后面会解释), 使得 3. 中的⽗亲不认⼉子对答案⽆任何影响, 同时, 也使一些叶⼦结点单独构成一棵 Splay 辅助树成为可能 -5. 由于辅助树的以上性质, 我们维护任何操作都不不需要维护原树, 辅助树可以在任何情况下拿出一个唯一的原树, 我们只需要维护辅助树即可。(本句来源自 大爷 @PoPoQQQ 的 PPT) +3. ⼀棵 Splay 的根节点的 Father 指向它在辅助树中的父亲结点。但是它父亲结点的 ch 并没有指向这个点的。即父亲不不⼀定认⼉子,⽽⼉子能找到⽗亲。 +4. 由于 LCT 的 Access 操作(后面会解释),使得 3. 中的⽗亲不认⼉子对答案⽆任何影响,同时,也使一些叶⼦结点单独构成一棵 Splay 辅助树成为可能 +5. 由于辅助树的以上性质,我们维护任何操作都不不需要维护原树,辅助树可以在任何情况下拿出一个唯一的原树,我们只需要维护辅助树即可。(本句来源自 大爷 @PoPoQQQ 的 PPT) -在本文里,你可以认为一些 Splay 构成了一个辅助树,每棵辅助树维护的是一棵树,一些辅助树构成了 LCT ,其维护的是整个森林。 +在本文里,你可以认为一些 Splay 构成了一个辅助树,每棵辅助树维护的是一棵树,一些辅助树构成了 LCT,其维护的是整个森林。 -- 现在我们有⼀棵原树, 如图。 -- 加粗边是实边, 虚线边是虚边 +- 现在我们有⼀棵原树,如图。 +- 加粗边是实边,虚线边是虚边 ![lct9](./images/lct9.png) -- 由刚刚的定义, 辅助树的结构如下 +- 由刚刚的定义,辅助树的结构如下 ![lct10](./images/lct10.png) @@ -87,63 +87,63 @@ - 原树中的实链 : 在辅助树中节点都在一棵 Splay 中 -- 原树中的虚链 : 在辅助树中, 子节点所在 Splay 的 Father 指向父节点, 但是父节点的两个儿子都不指向子节点。 +- 原树中的虚链 : 在辅助树中,子节点所在 Splay 的 Father 指向父节点,但是父节点的两个儿子都不指向子节点。 -- 注意: 原树的根 ≠辅助树的根。 +- 注意:原树的根 ≠辅助树的根。 - 原树的 Father 指向 ≠辅助树的 Father 指向。 - 辅助树是可以在满足辅助树、Splay 的性质下任意换根的。 -- 虚实链变换可以轻松在辅助树上完成, 这也就是实现了动态维护树链剖分。 +- 虚实链变换可以轻松在辅助树上完成,这也就是实现了动态维护树链剖分。 ### 接下来要用到的变量声明 -- `ch[N][2]` 左右⼉子 -- `f[N]` ⽗亲指向 +- `ch[N][2]` 左右⼉子 +- `f[N]` ⽗亲指向 -- `sum[N]` 路径权值和 -- `val[N]` 点权 +- `sum[N]` 路径权值和 +- `val[N]` 点权 -- `tag[N]` 翻转标记 -- `laz[N]` 权值标记 +- `tag[N]` 翻转标记 +- `laz[N]` 权值标记 - Other_Vars ### 函数声明 -#### ⼀般数据结构函数 (字面意思) +#### ⼀般数据结构函数(字面意思) 1. PushUp(x) 2. PushDown(x) -#### Splay 系函数 (不会多做解释) +#### Splay 系函数(不会多做解释) 1. Get(x) 获取 x 是父亲的哪个⼉子。 -2. Splay(x) 通过和 Rotate 操作联动实现把 x 旋转到 当前 Splay 的根。 +2. Splay(x) 通过和 Rotate 操作联动实现把 x 旋转到当前 Splay 的根。 3. Rotate(x) 将 x 向上旋转一层的操作。 #### 新操作 1. IsRoot(x) 判断当前节点是否是所在 Splay 的根 -2. Access(x) 把从根到当前节点的所有点放在⼀条实链里, 使根到它成为一条实路径, 并且在同一棵 Splay 里里。 -3. Update(x) 在 Access 操作之后, 递归的从上到下 Pushdown 更更新信 息。 +2. Access(x) 把从根到当前节点的所有点放在⼀条实链里,使根到它成为一条实路径,并且在同一棵 Splay 里里。 +3. Update(x) 在 Access 操作之后,递归的从上到下 Pushdown 更更新信 息。 4. MakeRoot(x) 使 x 点成为整个辅助树的根。 5. Link(x, y) 在 x, y 两点间连⼀一条边。 6. Cut(x, y) 把 x, y 两点间边删掉。 7. Find(x) 找到 x 所在的 Splay 的根节点编号。 8. Fix(x, v) 修改 x 的点权为 v。 -9. Split(x, y) 提取出来 x, y 间的路路径, ⽅方便便做区间操作 +9. Split(x, y) 提取出来 x, y 间的路路径,⽅方便便做区间操作 ### 宏定义 -- `#define ls ch[p][0]` -- `#define rs ch[p][1]` +- `#define ls ch[p][0]` +- `#define rs ch[p][1]` ## 函数讲解 先从简单的来吧 -### `PushUp()` +### `PushUp()` ```cpp inline void PushUp(int p) { @@ -153,7 +153,7 @@ inline void PushUp(int p) { } ``` -### `PushDown()` +### `PushDown()` ```cpp inline void PushDown(int p) { @@ -164,7 +164,7 @@ inline void PushDown(int p) { } ``` -### `Splay() && Rotate()` +### `Splay() && Rotate()` 有些不一样了哦 @@ -187,11 +187,11 @@ inline void Splay(int x) { } ``` -如果上面的几个函数你看不懂,请移步[Splay](/ds/splay/) +如果上面的几个函数你看不懂,请移步[Splay](/ds/splay/) 下面要开始 LCT 独有的函数了哦 -### `isRoot()` +### `isRoot()` ```cpp // 在前面我们已经说过,LCT 具有 如果一个儿子不是实儿子,他的父亲找不到它的性质 @@ -199,7 +199,7 @@ inline void Splay(int x) { #define isRoot(x) (ch[f[x]][0] != x && ch[f[x]][1] != x) ``` -### Access()  +### Access() ```cpp // Access 是 LCT @@ -212,36 +212,36 @@ inline void Access(int x) { } ``` -我们有这样一棵树,实线为实边,虚线为虚边 +我们有这样一棵树,实线为实边,虚线为虚边 ![pic1](./images/lct1.png) -- 它的辅助树可能长成这样 (构图方式不同可能 LCT 的结构也不同) +- 它的辅助树可能长成这样(构图方式不同可能 LCT 的结构也不同) - 每个绿框里是一棵 Splay。 ![pic2](./images/lct2.png) -- 现在我们要 Access(N), 把 A-N 的路径都变实, 拉成一棵 Splay +- 现在我们要 Access(N), 把 A-N 的路径都变实,拉成一棵 Splay ![pic3](./images/lct3.png) - 实现的方法是从下到上逐步更新 Splay - 首先我们要把 N 旋至当前 Splay 的根。 -- 为了保证 AuxTree 的性质, 原来 N——O 的实边要更改为虚边。 -- 由于认父不认子的性质, 我们可以单方面的把 N 的儿子改为 Null。 +- 为了保证 AuxTree 的性质,原来 N——O 的实边要更改为虚边。 +- 由于认父不认子的性质,我们可以单方面的把 N 的儿子改为 Null。 - 于是原来的 Aux 就从下图变成了下下图。 ![pic4](./images/lct4.png) ![pic](./images/lct5.png) -- 下一步, 我们把 N 指向的 Father-> I 也旋转到它 (I) 的 Splay 树根。 +- 下一步,我们把 N 指向的 Father-> I 也旋转到它 (I) 的 Splay 树根。 -- 原来的实边 I —— K 要去掉, 这时候我们把 I 的右儿子指向 N, 就得到了 I——L 这样一棵 Splay。 +- 原来的实边 I——K 要去掉,这时候我们把 I 的右儿子指向 N, 就得到了 I——L 这样一棵 Splay。 ![pic](./images/lct8.png) -- 接下来, 按照刚刚的操作步骤, 由于 I 的 Father 指向 H, 我们把 H 旋转到他所在 Splay Tree 的根, 然后把 H 的 rs 设为 I。 +- 接下来,按照刚刚的操作步骤,由于 I 的 Father 指向 H, 我们把 H 旋转到他所在 Splay Tree 的根,然后把 H 的 rs 设为 I。 - 之后的树是这样的。 @@ -266,9 +266,9 @@ inline void Access(int x) { 1. 把当前节点转到根。 2. 把儿子换成之前的节点。 3. 更新当前点的信息。 -4. 把当前点换成当前点的父亲, 继续操作。 +4. 把当前点换成当前点的父亲,继续操作。 -### `Update()` +### `Update()` ```cpp // 从上到下一层一层pushDown 即可 @@ -277,14 +277,14 @@ void Update(int p) { } ``` -### `makeRoot()` +### `makeRoot()` -- Make_Root() 的重要性丝毫不亚于 Access() 。 我们在需要维护路径信息的时候, 一定会出现路径深度无法严格递增的情况, 根据 Aux 的性质, 这种路径是不能出现在一棵 Splay 中的。 +- Make_Root() 的重要性丝毫不亚于 Access()。我们在需要维护路径信息的时候,一定会出现路径深度无法严格递增的情况,根据 Aux 的性质,这种路径是不能出现在一棵 Splay 中的。 - 这时候我们需要用到 Make_Root()。 -- Make_Root() 。的作用是使指定的点成为原树的根, 考虑如何实现这种操作。 -- 我们发现 Access(x) 后, x 在 Splay 中一定是深度最大的点 (从根到 x, 深度严格递增)。 -- 而变成根即是变成深度最小的点。我们 Splay(x) , 发现这时候 x 并没有右子树 (即所有点深度都比它浅)。那我们把 x 的左右儿子交换一下, 变成了 x 没有左子树, 在 Aux 意义上就是深度最小的点了, 即达到目的。 -- 所以我们交换左右儿子, 并给右儿子打一个翻转标记即可。(此时左儿子没有值)。 +- Make_Root()。的作用是使指定的点成为原树的根,考虑如何实现这种操作。 +- 我们发现 Access(x) 后,x 在 Splay 中一定是深度最大的点(从根到 x, 深度严格递增)。 +- 而变成根即是变成深度最小的点。我们 Splay(x) , 发现这时候 x 并没有右子树(即所有点深度都比它浅)。那我们把 x 的左右儿子交换一下,变成了 x 没有左子树,在 Aux 意义上就是深度最小的点了,即达到目的。 +- 所以我们交换左右儿子,并给右儿子打一个翻转标记即可。(此时左儿子没有值)。 ```cpp inline void makeRoot(int p) { @@ -294,9 +294,9 @@ inline void makeRoot(int p) { } ``` -### `Link()` +### `Link()` -- Link 两个点其实很简单, 先 Make_Root(x) , 然后把 x 的父亲指向 y 即可。显然, 这个操作肯定不能发生在同一棵树内 OTZ。记得先判一下。 +- Link 两个点其实很简单,先 Make_Root(x) , 然后把 x 的父亲指向 y 即可。显然,这个操作肯定不能发生在同一棵树内 OTZ。记得先判一下。 ```cpp inline void Link(int x, int p) { @@ -305,17 +305,17 @@ inline void Link(int x, int p) { } ``` -### `Split()` +### `Split()` -- Split 操作意义很简单, 就是拿出一棵 Splay , 维护的是 x 到 y 的路径。 -- 先 MakeRoot(x) , 然后 Access(y) 。如果要 y 做根, 再 Splay(y) 。 -- 就这三句话, 没写代码, 需要的时候可以直接打这三个就好辣! -- 另外 Split 这三个操作直接可以把需要的路径拿出到 y 的子树上, 那不是随便干嘛咯。 +- Split 操作意义很简单,就是拿出一棵 Splay , 维护的是 x 到 y 的路径。 +- 先 MakeRoot(x) , 然后 Access(y)。如果要 y 做根,再 Splay(y)。 +- 就这三句话,没写代码,需要的时候可以直接打这三个就好辣! +- 另外 Split 这三个操作直接可以把需要的路径拿出到 y 的子树上,那不是随便干嘛咯。 -### `Cut()` +### `Cut()` -- Cut 有两种情况, 保证合法和不一定保证合法。(废话) -- 如果保证合法, 直接 split(x, y) , 这时候 y 是根, x 一定是它的儿子, 双向断开即可 , 就像这样: +- Cut 有两种情况,保证合法和不一定保证合法。(废话) +- 如果保证合法,直接 split(x, y) , 这时候 y 是根,x 一定是它的儿子,双向断开即可 , 就像这样: ```cpp inline void Cut(int x, int p) { @@ -323,20 +323,20 @@ inline void Cut(int x, int p) { } ``` -如果是不保证合法, 我们需要判断一下是否有, 我选择使用 Map 存一下, 但是这里有一个利用性质的方法: +如果是不保证合法,我们需要判断一下是否有,我选择使用 Map 存一下,但是这里有一个利用性质的方法: -想要删边, 必须要满足如下三个条件: +想要删边,必须要满足如下三个条件: 1. x, y 连通。 2. x, y 的路径上没有其他的链。 3. x 没有右儿子。 -4. 总结一下, 上面三句话的意思就一个:x, y 有边。 +4. 总结一下,上面三句话的意思就一个:x, y 有边。 具体实现就留作一个思考题给大家。判断连通需要用到后面的 Find , 其他两点稍作思考分析一下结构就知道该怎么判断了。 -### `Find()` +### `Find()` -- Find() 其实就是找到当前辅助树的根。在 Access(p) 后, 再 splay(p)。这样根就是树里最小的那个, 一直往 ls 走, 沿途 PushDown 即可。 +- Find() 其实就是找到当前辅助树的根。在 Access(p) 后,再 splay(p)。这样根就是树里最小的那个,一直往 ls 走,沿途 PushDown 即可。 - 一直走到没有 ls, 非常简单。 ```cpp @@ -349,13 +349,13 @@ inline int Find(int p) { ### 一些提醒 -- 干点啥一定要想一想需不需要 PushUp 或者 PushDown, LCT 由于特别灵活的原因, 少 Pushdown 或者 Pushup 一次就可能把修改改到不该改的点上! -- 它的 rotate 和 splay 的不太一样, if(z) 一定要放在前面。 -- 它的 splay 就是旋转到根, 没有旋转到谁儿子的操作, 因为不需要。 +- 干点啥一定要想一想需不需要 PushUp 或者 PushDown, LCT 由于特别灵活的原因,少 Pushdown 或者 Pushup 一次就可能把修改改到不该改的点上! +- 它的 rotate 和 splay 的不太一样,if(z) 一定要放在前面。 +- 它的 splay 就是旋转到根,没有旋转到谁儿子的操作,因为不需要。 ## 一些题 -- BZOJ_2049 +- BZOJ_2049 - BZOJ_3282 - BZOJ_2002 - BZOJ_2631 diff --git a/docs/ds/monotonous-queue.md b/docs/ds/monotonous-queue.md index 665c3276..da454cf1 100644 --- a/docs/ds/monotonous-queue.md +++ b/docs/ds/monotonous-queue.md @@ -16,7 +16,7 @@ 顾名思义,单调队列的重点分为 "单调" 和 "队列" -"单调" 指的是元素的的 "规律"——递增 (或递减) +"单调" 指的是元素的的 "规律"——递增(或递减) "队列" 指的是元素只能从队头和队尾进行操作 @@ -32,10 +32,10 @@ Ps. 单调队列中的 "队列" 与正常的队列有一定的区别,稍后会 这就相当于维护了一个递减的队列,符合单调队列的定义,减少了重复的比较次数,不仅如此,由于维护出的队伍是查询范围内的且是递减的,队头必定是该查询区域内的最大值,因此输出时只需输出队头即可 -显而易见的是,在这样的算法中,每个数只要进队与出队各一次,因此时间复杂度被降到了 $O(N)$ +显而易见的是,在这样的算法中,每个数只要进队与出队各一次,因此时间复杂度被降到了 $O(N)$ 而由于查询区间长度是固定的,超出查询空间的值再大也不能输出,因此还需要 site 数组记录第 $i$ 个队中的数在原数组中的位置,以弹出越界的队头 [例题代码](https://www.luogu.org/paste/dze1lw3b) -Ps. 此处的 "队列" 跟普通队列的一大不同就在于可以从队尾进行操作, C++ 中有相似的数据结构 deque +Ps. 此处的 "队列" 跟普通队列的一大不同就在于可以从队尾进行操作,C++ 中有相似的数据结构 deque diff --git a/docs/ds/monotonous-stack.md b/docs/ds/monotonous-stack.md index 23b41399..fdea068e 100644 --- a/docs/ds/monotonous-stack.md +++ b/docs/ds/monotonous-stack.md @@ -10,7 +10,7 @@ 将一个元素插入单调栈时,为了维护栈的单调性,需要在保证将该元素插入到栈顶后整个栈满足单调性的前提下弹出最少的元素。 -例如,栈中自顶向下的元素为 ${1,3,5,10,30,50}$,插入元素 $20$ 时为了保证单调性需要依次弹出元素 $1,3,5,10$,操作后栈变为 $20,30,50$。 +例如,栈中自顶向下的元素为 ${1,3,5,10,30,50}$ ,插入元素 $20$ 时为了保证单调性需要依次弹出元素 $1,3,5,10$ ,操作后栈变为 $20,30,50$ 。 用伪代码描述如下: @@ -30,7 +30,7 @@ sta.push(x) ## 应用 ??? note "[POJ3250 Bad Hair Day](http://poj.org/problem?id=3250)" - 有 $N$ 头牛从左到右排成一排,每头牛有一个高度 $h_i$,设左数第 $i$ 头牛与「它右边第一头高度 $≥h_i$」的牛之间有 $c_i$ 头牛,试求 $\sum_{i=1}^{N} c_i$。 + 有 $N$ 头牛从左到右排成一排,每头牛有一个高度 $h_i$ ,设左数第 $i$ 头牛与「它右边第一头高度 $≥h_i$ 」的牛之间有 $c_i$ 头牛,试求 $\sum_{i=1}^{N} c_i$ 。 (可以左转 [洛谷 P2866](https://www.luogu.org/problemnew/show/P2866)) diff --git a/docs/ds/pb-ds/priority-queue.md b/docs/ds/pb-ds/priority-queue.md index d1b79ad6..89234ca3 100644 --- a/docs/ds/pb-ds/priority-queue.md +++ b/docs/ds/pb-ds/priority-queue.md @@ -1,6 +1,6 @@ -## `__gnu_pbds :: priority_queue` +## `__gnu_pbds :: priority_queue` -附 :[官方文档地址——复杂度及常数测试](https://gcc.gnu.org/onlinedocs/libstdc++/ext/pb_ds/pq_performance_tests.html#std_mod1) +附:[官方文档地址——复杂度及常数测试](https://gcc.gnu.org/onlinedocs/libstdc++/ext/pb_ds/pq_performance_tests.html#std_mod1) ```cpp #include @@ -10,24 +10,23 @@ __gnu_pbds ::priority_queue ## 模板形参 -- `T` : 储存的元素类型 -- `Compare` : 提供严格的弱序比较类型 -- `Tag` : 是 `__gnu_pbds` 提供的不同的五种堆,Tag 参数默认是 `pairing_heap_tag` - 五种分别是 : - - `pairing_heap_tag`:配对堆 - 官方文档认为在非原生元素 (如自定义结构体 / `std :: string` / `pair`) 中,配对堆表现最好 - - `binary_heap_tag`:二叉堆 +- `T` : 储存的元素类型 +- `Compare` : 提供严格的弱序比较类型 +- `Tag` : 是 `__gnu_pbds` 提供的不同的五种堆,Tag 参数默认是 `pairing_heap_tag` 五种分别是: + - `pairing_heap_tag` :配对堆 + 官方文档认为在非原生元素(如自定义结构体/ `std :: string` / `pair` ) 中,配对堆表现最好 + - `binary_heap_tag` :二叉堆 官方文档认为在原生元素中二叉堆表现最好,不过我测试的表现并没有那么好 - - `binomial_heap_tag`:二项堆 - 二项堆在合并操作的表现要优于配对堆 \* 但是其取堆顶元素的 - - `rc_binomial_heap_tag`:冗余计数二项堆 - - `thin_heap_tag`:除了合并的复杂度都和 Fibonacci 堆一样的一个 tag -- `Allocator`:空间配置器,由于 OI 中很少出现,故这里不做讲解 + - `binomial_heap_tag` :二项堆 + 二项堆在合并操作的表现要优于配对堆\*但是其取堆顶元素的 + - `rc_binomial_heap_tag` :冗余计数二项堆 + - `thin_heap_tag` :除了合并的复杂度都和 Fibonacci 堆一样的一个 tag +- `Allocator` :空间配置器,由于 OI 中很少出现,故这里不做讲解 由于本篇文章只是提供给学习算法竞赛的同学们,故对于后四个 tag 只会简单的介绍复杂度,第一个会介绍成员函数和使用方法。 经作者本机 Core i5@3.1 GHz On macOS 测试堆的基础操作,结合 GNU 官方的复杂度测试,Dijkstra 测试,都表明: -至少对于 OIer 来讲,除了配对堆的其他 4 个 tag 都是鸡肋,要么没用,要么常数大到不如 std 的,且有可能造成 MLE,故这里只推荐用默认的配对堆。同样,配对堆也优于 `algorithm` 库中的 `make_heap()`。 +至少对于 OIer 来讲,除了配对堆的其他 4 个 tag 都是鸡肋,要么没用,要么常数大到不如 std 的,且有可能造成 MLE,故这里只推荐用默认的配对堆。同样,配对堆也优于 `algorithm` 库中的 `make_heap()` 。 ## 构造方式 @@ -41,24 +40,24 @@ __gnu_pbds ::priority_queue ## 成员函数 -1. `push()`: 向堆中压入一个元素, 返回该元素位置的迭代器 -2. `pop()`: 将堆顶元素弹出 -3. `top()`: 返回堆顶元素 -4. `size()`返回元素个数 -5. `empty()`返回是否非空 -6. `modify(point_iterator, const key)` : 把迭代器位置的 key 修改为传入的 key,并对底层储存结构进行排序 -7. `erase(point_iterator)` : 把迭代器位置的键值从堆中擦除 -8. `join(__gnu_pbds :: priority_queue &other)`: 把 other 合并到 \* this 并把 other 清空。 +1. `push()` : 向堆中压入一个元素,返回该元素位置的迭代器 +2. `pop()` : 将堆顶元素弹出 +3. `top()` : 返回堆顶元素 +4. `size()` 返回元素个数 +5. `empty()` 返回是否非空 +6. `modify(point_iterator, const key)` : 把迭代器位置的 key 修改为传入的 key,并对底层储存结构进行排序 +7. `erase(point_iterator)` : 把迭代器位置的键值从堆中擦除 +8. `join(__gnu_pbds :: priority_queue &other)` : 把 other 合并到\*this 并把 other 清空。 使用的 `tag` 决定了每个操作的时间复杂度: -| | push | pop | modify | erase | Join | -| -------------------- | ------------------------------------ | :----------------------------------- | ------------------------------------ | -------------------------------------- | ----------------- | -| Pairing_heap_tag | $O(1)$ | 最坏$\Theta(n)$ 均摊$\Theta(\log(n))$ | 最坏$\Theta(n)$ 均摊$\Theta(\log(n))$ | 最坏$\Theta(n)$ 均摊$\Theta(\log(n))$ | $O(1)$ | -| Binary_heap_tag | 最坏$\Theta(n)$ 均摊$\Theta(\log(n))$ | 最坏$\Theta(n)$ 均摊$\Theta(\log(n))$ | $\Theta(n)$ | $\Theta(n)$ | $\Theta(n)$ | -| Binomial_heap_tag | 最坏$\Theta(\log(n))$ 均摊$O(1)$ | $\Theta(\log(n))$ | $\Theta(\log(n))$ | $\Theta(\log(n))$ | $\Theta(\log(n))$ | -| Rc_Binomial_heap_tag | $O(1)$ | $\Theta(\log(n))$ | $\Theta(\log(n))$ | $\Theta(\log(n))$ | $\Theta(\log(n))$ | -| Thin_heap_tag | $O(1)$ | 最坏$\Theta(n)$ 均摊$\Theta(\log(n))$ | 最坏$\Theta(\log(n))$ 均摊$O(1)$ | 最坏$\Theta(n)$ 0 均摊$\Theta(\log(n))$ | $\Theta(n)$ | +| | push | pop | modify | erase | Join | +| -------------------- | ------------------------------------ | :----------------------------------- | ------------------------------------ | -------------------------------------- | ------------------- | +| Pairing_heap_tag | $O(1)$ | 最坏 $\Theta(n)$ 均摊 $\Theta(\log(n))$ | 最坏 $\Theta(n)$ 均摊 $\Theta(\log(n))$ | 最坏 $\Theta(n)$ 均摊 $\Theta(\log(n))$ | $O(1)$ | +| Binary_heap_tag | 最坏 $\Theta(n)$ 均摊 $\Theta(\log(n))$ | 最坏 $\Theta(n)$ 均摊 $\Theta(\log(n))$ | $\Theta(n)$ | $\Theta(n)$ | $\Theta(n)$ | +| Binomial_heap_tag | 最坏 $\Theta(\log(n))$ 均摊 $O(1)$ | $\Theta(\log(n))$ | $\Theta(\log(n))$ | $\Theta(\log(n))$ | $\Theta(\log(n))$ | +| Rc_Binomial_heap_tag | $O(1)$ | $\Theta(\log(n))$ | $\Theta(\log(n))$ | $\Theta(\log(n))$ | $\Theta(\log(n))$ | +| Thin_heap_tag | $O(1)$ | 最坏 $\Theta(n)$ 均摊 $\Theta(\log(n))$ | 最坏 $\Theta(\log(n))$ 均摊 $O(1)$ | 最坏 $\Theta(n)$ 0 均摊 $\Theta(\log(n))$ | $\Theta(n)$ | ## 示例 diff --git a/docs/ds/persistent-balanced.md b/docs/ds/persistent-balanced.md index 096a8bb3..a87fbf76 100644 --- a/docs/ds/persistent-balanced.md +++ b/docs/ds/persistent-balanced.md @@ -4,48 +4,48 @@ Split-Merge Treap ## 对于无旋 Treap 的提示 -看楼上的 [Treap 词条](/ds/treap/) +看楼上的[Treap 词条](/ds/treap/) -**OI 常用的可持久化平衡树** 一般就是 **可持久化无旋转 Treap** 所以推荐首先学习楼上的 **无旋转 Treap** +**OI 常用的可持久化平衡树**一般就是**可持久化无旋转 Treap**所以推荐首先学习楼上的**无旋转 Treap** -## 思想 / 做法 +## 思想/做法 我们来看看旋转的 Treap,为什么不能可持久化呢? 如果带旋转,那么就会**破环原有的父子关系**,破环原有的路径和树形态,这是可持久化无法接受的。 -如果把 Treap 变为非旋转的,我们发现可以通过可持久化 **Merge** 和 **Split** 操作就可以完成可持久化. +如果把 Treap 变为非旋转的,我们发现可以通过可持久化**Merge**和**Split**操作就可以完成可持久化。 -「一切可支持操作都可以通过 **Merge** **Split** **Newnode** **Build** 完成」,而 **Build** 操作只用于建造无需理会,**Newnode**(新建节点) 就是用来可持久化的工具。 +「一切可支持操作都可以通过**Merge****Split****Newnode****Build**完成」,而**Build**操作只用于建造无需理会,**Newnode**(新建节点)就是用来可持久化的工具。 -我们来观察一下 **Merge** 和 **Split** ,我们会发现它们都是由上而下的操作! +我们来观察一下**Merge**和**Split**,我们会发现它们都是由上而下的操作! 因此我们完全可以**参考线段树的可持久化操作**对它进行可持久化。 ## 可持久化操作 -**可持久化**是对**数据结构**的一种操作,即保留历史信息,使得在后面可以调用之前的历史版本. +**可持久化**是对**数据结构**的一种操作,即保留历史信息,使得在后面可以调用之前的历史版本。 对于**可持久化线段树**来说,每一次新建历史版本就是把**沿途的修改路径**复制出来 -那么对可持久化 Treap (目前国内 OI 常用的版本) 来说: +那么对可持久化 Treap(目前国内 OI 常用的版本)来说: -在复制一个节点 $X_{a}$($X$ 节点的第 $a$ 个版本) 的新版本 $X_{a+1}$ ($X$ 节点的第 $a+1$ 个版本) 以后: +在复制一个节点 $X_{a}$ ( $X$ 节点的第 $a$ 个版本)的新版本 $X_{a+1}$ ( $X$ 节点的第 $a+1$ 个版本)以后: -- 如果某个儿子节点 $Y$ 不用修改信息,那么就把 $X_{a+1}$ 的指针直接指向 $Y_{a}$ ($Y$ 节点的第 $a$ 个版本) 即可。 -- 反之,如果要修改 $Y$ ,那么就在**递归到下层**时**新建** $Y_{a+1}$ ($Y$ 节点的第 $a+1$ 个版本) 这个新节点用于**存储新的信息**,同时把 $X_{a+1}$ 的指针指向 $Y_{a+1}$ ($Y$ 节点的第 $a+1$ 个版本)。 +- 如果某个儿子节点 $Y$ 不用修改信息,那么就把 $X_{a+1}$ 的指针直接指向 $Y_{a}$ ( $Y$ 节点的第 $a$ 个版本)即可。 +- 反之,如果要修改 $Y$ ,那么就在**递归到下层**时**新建** $Y_{a+1}$ ( $Y$ 节点的第 $a+1$ 个版本)这个新节点用于**存储新的信息**,同时把 $X_{a+1}$ 的指针指向 $Y_{a+1}$ ( $Y$ 节点的第 $a+1$ 个版本)。 ## 可持久化 -需要的东西: +需要的东西: -- 一个 struct 数组 存**每个节点**的信息 (一般叫做 tree 数组); (当然写**指针版**平衡树的大佬就可以考虑不用这个数组了) +- 一个 struct 数组 存**每个节点**的信息(一般叫做 tree 数组);(当然写**指针版**平衡树的大佬就可以考虑不用这个数组了) - 一个**根节点数组**,存每个版本的_树根_,每次查询版本信息时就从**根数组存的节点**开始; -- split() 分裂 **从树中分裂出两棵树** +- split() 分裂**从树中分裂出两棵树** -- merge() 合并 **把两棵树按照随机权值合并** +- merge() 合并**把两棵树按照随机权值合并** - newNode() 新建一个节点 @@ -55,11 +55,11 @@ Split-Merge Treap 对于**分裂操作**,每次分裂路径时**新建节点**指向分出来的路径,用 std::pair 存新分裂出来的两棵树的根。 -std::pair <int,int> split(x,k) 返回一个 std::pair; +std::pair<int,int> split(x,k) 返回一个 std::pair; 表示把 $_x$ 为根的树的前 $k$ 个元素放在**一棵树**中,剩下的节点构成在另一棵树中,返回这两棵树的根(first 是第一棵树的根,second 是第二棵树的)。 -- 如果 $x$ 的**左子树**的 $key ≥ k$,那么**直接递归进左子树**,把左子树分出来的第二颗树和当前的 x **右子树**合并。 +- 如果 $x$ 的**左子树**的 $key ≥ k$ ,那么**直接递归进左子树**,把左子树分出来的第二颗树和当前的 x**右子树**合并。 - 否则递归**右子树**。 ```c++ @@ -89,7 +89,7 @@ static std::pair _split(int _x, int k) { int merge(x,y) 返回 merge 出的树的根。 -同样递归实现。如果 **x 的随机权值** > **y 的随机权值** ,则 $merge(x_{rc},y)$,否则 $merge(x,y_{lc})$。 +同样递归实现。如果**x 的随机权值**>**y 的随机权值**,则 $merge(x_{rc},y)$ ,否则 $merge(x,y_{lc})$ 。 ```c++ static int _merge(int _x, int _y) { @@ -113,7 +113,7 @@ static int _merge(int _x, int _y) { ### 题目背景 -本题为题目 **普通平衡树** 的可持久化加强版。 +本题为题目**普通平衡树**的可持久化加强版。 数据已经经过强化 @@ -139,7 +139,7 @@ static int _merge(int _x, int _y) { ### 输入格式 -第一行为 n,表示操作的个数, 下面 n 行每行有两个数 opt 和 x,opt 表示操作的序号 $(1 \leq x \leq le6)$。 +第一行为 n,表示操作的个数,下面 n 行每行有两个数 opt 和 x,opt 表示操作的序号 $(1 \leq x \leq le6)$ 。 ### 输出格式 @@ -147,18 +147,18 @@ static int _merge(int _x, int _y) { ## 题解简述 -就是**普通平衡树**一题的可持久化版,操作和该题类似.. +就是**普通平衡树**一题的可持久化版,操作和该题类似…… 只是使用了可持久化的 merge 和 split 操作 ## 推荐的练手题 -1. luogu P3919 可持久化数组 (模板题) +1. luogu P3919 可持久化数组(模板题) 2. codeforces 702F T-shirt ## 另外 -1. 可持久化平衡树可以用来维护动态凸包,仙人掌等东西,如果读者有兴趣可以阅读相应的 [**计算几何**](/geometry) 知识,再来食用。 +1. 可持久化平衡树可以用来维护动态凸包,仙人掌等东西,如果读者有兴趣可以阅读相应的[**计算几何**](/geometry)知识,再来食用。 2. Zip Tree 作为一种新的数据结构在 2018.8 月由 Robert E. Tarjan - Caleb C. Levy - Stephen Timmel 提出,可以去了解一下~ diff --git a/docs/ds/persistent-in-bit.md b/docs/ds/persistent-in-bit.md index 245ecd15..0215db39 100644 --- a/docs/ds/persistent-in-bit.md +++ b/docs/ds/persistent-in-bit.md @@ -1,24 +1,24 @@ -[静态区间 k 小值](https://www.luogu.org/problemnew/show/P3834) 的问题可以用 [主席树](/ds/persistent-seg/) 在 $O(n\log_2 n)$ 的时间复杂度内解决。 +[静态区间 k 小值](https://www.luogu.org/problemnew/show/P3834)的问题可以用[主席树](/ds/persistent-seg/)在 $O(n\log_2 n)$ 的时间复杂度内解决。 如果区间变成动态的呢?即,如果还要求支持一种操作:单点修改某一位上的值,又该怎么办呢? -??? note " 例题 [洛谷 P3380 【模板】二逼平衡树(树套树)](https://www.luogu.org/problemnew/show/P3380)" +??? note " 例题[洛谷 P3380【模板】二逼平衡树(树套树)](https://www.luogu.org/problemnew/show/P3380)" -??? note " 例题 [洛谷 P2617 Dynamic Rankings](https://www.luogu.org/problemnew/show/P2617)" +??? note " 例题[洛谷 P2617 Dynamic Rankings](https://www.luogu.org/problemnew/show/P2617)" -如果用 [线段树套平衡树](/ds/balanced-in-seg/) 中所论述的,用线段树套平衡树,即对于线段树的每一个节点,对于其所表示的区间维护一个平衡树,然后用二分来查找 $k$ 小值。由于每次查询操作都要覆盖多个区间,即有多个节点,但是平衡树并不能多个值一起查找,所以时间复杂度是 $O(n\log_2^3 n)$ ,并不是最优的。 +如果用[线段树套平衡树](/ds/balanced-in-seg/)中所论述的,用线段树套平衡树,即对于线段树的每一个节点,对于其所表示的区间维护一个平衡树,然后用二分来查找 $k$ 小值。由于每次查询操作都要覆盖多个区间,即有多个节点,但是平衡树并不能多个值一起查找,所以时间复杂度是 $O(n\log_2^3 n)$ ,并不是最优的。 -思路是,把二分答案的操作和查询小于一个值的数的数量两种操作结合起来。最好的方法是使用 ** 线段树套主席树 ** 。 +思路是,把二分答案的操作和查询小于一个值的数的数量两种操作结合起来。最好的方法是使用**线段树套主席树**。 -说是主席树其实不准确,因为并不是对线段树的可持久化,各个线段树之间也没有像主席树各版本之间的强关联性,所以称为 ** 动态开点权值线段树 ** 更为确切。 +说是主席树其实不准确,因为并不是对线段树的可持久化,各个线段树之间也没有像主席树各版本之间的强关联性,所以称为**动态开点权值线段树**更为确切。 思路类似于线段树套平衡树,即对于线段树所维护的每个区间,建立一个动态开点权值线段树,表示其所维护的区间的值。 -在修改操作进行时,先在线段树上从上往下跳到被修改的点,删除所经过的点所指向的动态开点权值线段树上的原来的值,然后插入新的值,要经过 $O(\log_2 n)$ 个线段树上的节点,在动态开点权值线段树上一次修改操作是 $O(\log_2 n)$ 的,所以修改操作的时间复杂度为 $O(\log_2^2 n)$。 +在修改操作进行时,先在线段树上从上往下跳到被修改的点,删除所经过的点所指向的动态开点权值线段树上的原来的值,然后插入新的值,要经过 $O(\log_2 n)$ 个线段树上的节点,在动态开点权值线段树上一次修改操作是 $O(\log_2 n)$ 的,所以修改操作的时间复杂度为 $O(\log_2^2 n)$ 。 -在查询答案时,先取出该区间覆盖在线段树上的所有点,然后用类似于静态区间 $k$ 小值的方法,将这些点一起向左儿子或向右儿子跳。如果所有这些点左儿子存储的值大于等于 $k$ ,则往左跳,否则往右跳。由于最多只能覆盖 $O(\log_2 n)$ 个节点,所以最多一次只有这么多个节点向下跳,时间复杂度为 $O(\log_2^2 n)$ 。 +在查询答案时,先取出该区间覆盖在线段树上的所有点,然后用类似于静态区间 $k$ 小值的方法,将这些点一起向左儿子或向右儿子跳。如果所有这些点左儿子存储的值大于等于 $k$ ,则往左跳,否则往右跳。由于最多只能覆盖 $O(\log_2 n)$ 个节点,所以最多一次只有这么多个节点向下跳,时间复杂度为 $O(\log_2^2 n)$ 。 -由于线段树的常数较大,在实现中往往使用常数更小且更方便处理前缀和的 ** 树状数组 ** 实现。 +由于线段树的常数较大,在实现中往往使用常数更小且更方便处理前缀和的**树状数组**实现。 给出一种代码实现: diff --git a/docs/ds/persistent-seg.md b/docs/ds/persistent-seg.md index 9c0418dd..49a90131 100644 --- a/docs/ds/persistent-seg.md +++ b/docs/ds/persistent-seg.md @@ -1,9 +1,8 @@ ## 主席树 -主席树全称是可持久化权值线段树,参见 [知乎讨论](https://www.zhihu.com/question/59195374)。 +主席树全称是可持久化权值线段树,参见[知乎讨论](https://www.zhihu.com/question/59195374)。 -!!! warning - **函数式线段树** 是指使用函数式编程思想的线段树。在函数式编程思想中,将计算机运算视为数学函数,并避免可改变的状态或变量。不难发现,函数式线段树是 [完全可持久化](/ds/persistent/#_2) 的。 +!!! warning**函数式线段树**是指使用函数式编程思想的线段树。在函数式编程思想中,将计算机运算视为数学函数,并避免可改变的状态或变量。不难发现,函数式线段树是[完全可持久化](/ds/persistent/#_2)的。 面对眼前的区间第 $k$ 小问题,你该何从下手? @@ -22,23 +21,23 @@ 所以我们只要在记录左右儿子的基础上存一下插入每个数的时候的根节点就可以持久化辣。 我们把问题简化一下:每次求 $[1,r]$ 区间内的 $k$ 小值。 -怎么做呢?只需要找到插入 r 时的根节点版本,然后用普通权值线段树(有的叫键值线段树 / 值域线段树)做就行了。 +怎么做呢?只需要找到插入 r 时的根节点版本,然后用普通权值线段树(有的叫键值线段树/值域线段树)做就行了。 那么这个相信大家很简单都能理解,把问题扩展到原问题——求 $[l,r]$ 区间 $k$ 小值。 这里我们再联系另外一个知识理解:**前缀和**。 这个小东西巧妙运用了区间减法的性质,通过预处理从而达到 $O(1)$ 回答每个询问。 那么我们阔以发现,主席树统计的信息也满足这个性质。 -所以…… 如果需要得到 $[l,r]$ 的统计信息,只需要用 $[1,r]$ 的信息减去 $[1,l - 1]$ 的信息就行了。 +所以……如果需要得到 $[l,r]$ 的统计信息,只需要用 $[1,r]$ 的信息减去 $[1,l - 1]$ 的信息就行了。 那么至此,该问题解决!(完结撒花) 关于空间问题,我们分析一下:由于我们是动态开点的,所以一棵线段树只会出现 $2n-1$ 个结点。 然后,有 $n$ 次修改,每次增加 $\log{n}$ 个结点。那么最坏情况结点数会达到 $2n-1+n\log{n}$ ,那么此题的 $n \leq 10^5$ ,通过计算得到 $\lceil\log_2{10^5}\rceil = 17$ 。 -那么把 $n$ 和 $\log$ 的结果代入这个式子,变成 $2\times 10^5-1+17\times 10^5$,忽略掉 $-1$ ,大概就是 $19\times 10^5$ 。 +那么把 $n$ 和 $\log$ 的结果代入这个式子,变成 $2\times 10^5-1+17\times 10^5$ ,忽略掉 $-1$ ,大概就是 $19\times 10^5$ 。 -算了这么一大堆,I tell you: 千万不要吝啬空间!保守一点,直接上个 $2^5\times 10^5$,接近原空间的两倍(即`n << 5`)。 -(较真的同学请注意,如果你真的很吝啬,可以自己造个数据输出一下结点数量,但是如果数据没造好把自己卡掉了就 ~~尴尬了~~ 赖你了) +算了这么一大堆,I tell you: 千万不要吝啬空间!保守一点,直接上个 $2^5\times 10^5$ ,接近原空间的两倍(即 `n << 5` )。 +(较真的同学请注意,如果你真的很吝啬,可以自己造个数据输出一下结点数量,但是如果数据没造好把自己卡掉了就~~尴尬了~~赖你了) 代码: diff --git a/docs/ds/persistent-trie.md b/docs/ds/persistent-trie.md index 072e539b..96501d1f 100644 --- a/docs/ds/persistent-trie.md +++ b/docs/ds/persistent-trie.md @@ -1,8 +1,8 @@ 可持久化 Trie 的方式和可持久化线段树的方式是相似的,即每次只修改被添加或值被修改的节点,而保留没有被改动的节点,在上一个版本的基础上连边,使最后每个版本的 Trie 树的根遍历所能分离出的 Trie 树都是完整且包含全部信息的。 -大部分的可持久化 Trie 题中, Trie 都是以 01-Trie 的形式出现的。 +大部分的可持久化 Trie 题中,Trie 都是以 01-Trie 的形式出现的。 -??? note " 例题 [洛谷 P4735 最大异或和](https://www.luogu.org/problemnew/show/P4735)" +??? note " 例题[洛谷 P4735 最大异或和](https://www.luogu.org/problemnew/show/P4735)" 对一个长度为 $n$ 的数组 $a$ 维护以下操作: diff --git a/docs/ds/queue.md b/docs/ds/queue.md index b971b611..9f46dc12 100644 --- a/docs/ds/queue.md +++ b/docs/ds/queue.md @@ -1,13 +1,13 @@ -队列,英文名是 queue,在 C++ STL 中有 [std::queue](https://en.cppreference.com/w/cpp/container/queue) 和 [std::priority_queue](https://en.cppreference.com/w/cpp/container/priority_queue)。 +队列,英文名是 queue,在 C++ STL 中有[std::queue](https://en.cppreference.com/w/cpp/container/queue)和[std::priority_queue](https://en.cppreference.com/w/cpp/container/priority_queue)。 先进入队列的元素一定先出队列,因此队列通常也被称为先进先出(first in first out)表,简称 FIFO 表。 -注:`std::stack` 和 `std::queue` 都是容器适配器,默认底层容器为 `std::deque`(双端队列)。 +注: `std::stack` 和 `std::queue` 都是容器适配器,默认底层容器为 `std::deque` (双端队列)。 通常用一个数组模拟一个队列,用两个指针:front 和 rear 分别表示队列头部和尾部。 在入队的时候将 rear 后移,在出队的时候将 front 后移。 -这样会导致一个问题:随着时间的推移,整个队列会向数组的尾部移动,一旦到达数组的最末端,即使数组的前端还有空闲位置,再进行入队操作也会导致溢出。(这种数组上实际有空闲位置而发生了上溢的现象称为是 “假溢出”。 +这样会导致一个问题:随着时间的推移,整个队列会向数组的尾部移动,一旦到达数组的最末端,即使数组的前端还有空闲位置,再进行入队操作也会导致溢出。(这种数组上实际有空闲位置而发生了上溢的现象称为是“假溢出”。 -解决假溢出的办法是采用循环的方式来组织存放队列元素的数组,即将数组下标为 0 的位置看做是最后一个位置的后继。(`x` 的后继为 `(x + 1) % Size`)。这样就形成了循环队列。 +解决假溢出的办法是采用循环的方式来组织存放队列元素的数组,即将数组下标为 0 的位置看做是最后一个位置的后继。( `x` 的后继为 `(x + 1) % Size` )。这样就形成了循环队列。 diff --git a/docs/ds/scapegoat.md b/docs/ds/scapegoat.md index 32673155..93d3deff 100644 --- a/docs/ds/scapegoat.md +++ b/docs/ds/scapegoat.md @@ -1,4 +1,4 @@ -** 替罪羊树 ** 是一种依靠重构操作维持平衡的重量平衡树。替罪羊树会在插入、删除操作时,检测途经的节点,若发现失衡,则将以该节点为根的子树重构。 +**替罪羊树**是一种依靠重构操作维持平衡的重量平衡树。替罪羊树会在插入、删除操作时,检测途经的节点,若发现失衡,则将以该节点为根的子树重构。 我们在此实现一个可重的权值平衡树。 @@ -20,9 +20,9 @@ void Calc(int k) { ## 重构 -首先,如前所述,我们需要判定一个节点是否应重构。为此我们引入一个比例常数 $\alpha$(取值在 $(0.5,1)$,一般采用 $0.7$ 或 $0.8$),若某节点的子节点大小占它本身大小的比例超过 $\alpha$,则重构。 +首先,如前所述,我们需要判定一个节点是否应重构。为此我们引入一个比例常数 $\alpha$ (取值在 $(0.5,1)$ ,一般采用 $0.7$ 或 $0.8$ ),若某节点的子节点大小占它本身大小的比例超过 $\alpha$ ,则重构。 -另外由于我们采用惰性删除(删除只使用 `wn[k]--`),已删除节点过多也影响效率。因此若未被删除的子树大小占总大小的比例低于 $\alpha$,则亦重构。 +另外由于我们采用惰性删除(删除只使用 `wn[k]--` ),已删除节点过多也影响效率。因此若未被删除的子树大小占总大小的比例低于 $\alpha$ ,则亦重构。 ```cpp inline bool CanRbu(int k) { @@ -64,11 +64,11 @@ void Rbu(int& k) { ## 基本操作 -几种操作的处理方式较为类似,都规定了 ** 到达空结点 ** 与 ** 找到对应结点 ** 的行为,之后按 ** 小于向左、大于向右 ** 的方式向下递归。 +几种操作的处理方式较为类似,都规定了**到达空结点**与**找到对应结点**的行为,之后按**小于向左、大于向右**的方式向下递归。 ### 插入 -插入时,到达空结点则新建节点,找到对应结点则 `wn[k]++`。递归结束后,途经的节点可重构的要重构。 +插入时,到达空结点则新建节点,找到对应结点则 `wn[k]++` 。递归结束后,途经的节点可重构的要重构。 ```cpp void Ins(int& k, int p) { @@ -94,7 +94,7 @@ void Ins(int& k, int p) { ### 删除 -惰性删除,到达空结点则忽略,找到对应结点则 `wn[k]--`。递归结束后,可重构节点要重构。 +惰性删除,到达空结点则忽略,找到对应结点则 `wn[k]--` 。递归结束后,可重构节点要重构。 ```cpp void Del(int& k, int p) { @@ -137,7 +137,7 @@ int MyUprBd(int k, int p) { } ``` -以下是反义函数,相当于采用 `std::greater<>` 比较,即返回权值严格小于某值的最大名次。查询一个数的排名可以用 `MyUprGrt(rt, x) + 1`。 +以下是反义函数,相当于采用 `std::greater<>` 比较,即返回权值严格小于某值的最大名次。查询一个数的排名可以用 `MyUprGrt(rt, x) + 1` 。 ```cpp int MyUprGrt(int k, int p) { diff --git a/docs/ds/seg-in-seg.md b/docs/ds/seg-in-seg.md index 350e6a6e..f63ccd9e 100644 --- a/docs/ds/seg-in-seg.md +++ b/docs/ds/seg-in-seg.md @@ -8,16 +8,16 @@ ## 空间复杂度 -通常情况下,我们不可能对于外层线段树的每一个结点都建立一颗子线段树,空间需求过大。树套树一般采取动态开点的策略。单次修改,我们会涉及到外层线段树的 $\log{n}$ 个节点,且对于每个节点的子树涉及 $\log{n}$ 个节点,所以单次修改产生的空间最多为 $\log^2{n}$。 +通常情况下,我们不可能对于外层线段树的每一个结点都建立一颗子线段树,空间需求过大。树套树一般采取动态开点的策略。单次修改,我们会涉及到外层线段树的 $\log{n}$ 个节点,且对于每个节点的子树涉及 $\log{n}$ 个节点,所以单次修改产生的空间最多为 $\log^2{n}$ 。 ## 时间复杂度 -对于询问操作,我们考虑我们在外层线段树上进行 $\log{n}$ 次操作,每次操作会在一个内层线段树上进行 $\log{n}$ 次操作,所以时间复杂度为 $\log^2{n}$。 -修改操作,与询问操作复杂度相同,也为 $\log^2{n}$。 +对于询问操作,我们考虑我们在外层线段树上进行 $\log{n}$ 次操作,每次操作会在一个内层线段树上进行 $\log{n}$ 次操作,所以时间复杂度为 $\log^2{n}$ 。 +修改操作,与询问操作复杂度相同,也为 $\log^2{n}$ 。 ## 经典例题 -[陌上花开](https://www.lydsy.com/JudgeOnline/problem.php?id=3262) 将第一维排序处理,然后用树套树维护第二维和第三维。 +[陌上花开](https://www.lydsy.com/JudgeOnline/problem.php?id=3262)将第一维排序处理,然后用树套树维护第二维和第三维。 ## 示例代码 @@ -77,4 +77,4 @@ void vec_insert(int &k, int l, int r, int loc) { ## 相关算法 -面对多维度信息的题目时,如果题目没有要求强制在线,我们还可以考虑 **CDQ 分治**,或者**整体二分**等分治算法,来避免使用高级数据结构,减少代码实现难度。 +面对多维度信息的题目时,如果题目没有要求强制在线,我们还可以考虑**CDQ 分治**,或者**整体二分**等分治算法,来避免使用高级数据结构,减少代码实现难度。 diff --git a/docs/ds/segment.md b/docs/ds/segment.md index cf737120..fcb39712 100644 --- a/docs/ds/segment.md +++ b/docs/ds/segment.md @@ -6,7 +6,7 @@ OI 中最常用的数据结构之一,不学不行啊 OvO ## 线段树是什么 -> 线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为 $O(\log N)$。而未优化的空间复杂度为 $2N$,因此有时需要离散化让空间压缩。——From 度娘 +> 线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为 $O(\log N)$ 。而未优化的空间复杂度为 $2N$ ,因此有时需要离散化让空间压缩。——From 度娘 反正就是一种可以在很短的时间内对某个区间进行操作的数据结构。 @@ -23,26 +23,22 @@ OI 中最常用的数据结构之一,不学不行啊 OvO 下面我来举个栗子: -我们有个大小为 $5$ 的数组 $a=\{10,11,12,13,14\}$ 要进行区间求和操作,现在我们要怎么把这个数组存到线段树中(也可以说是转化成线段树)呢?我们这样子做:设线段树的根节点编号为 $1$,用数组 $d$ 来保存我们的线段树,$d[i]$ 用来保存编号为 $i$ 的节点的值(这里节点的值就是这个节点所表示的区间总和),如图所示: -![](./images/segt1.png) +我们有个大小为 $5$ 的数组 $a=\{10,11,12,13,14\}$ 要进行区间求和操作,现在我们要怎么把这个数组存到线段树中(也可以说是转化成线段树)呢?我们这样子做:设线段树的根节点编号为 $1$ ,用数组 $d$ 来保存我们的线段树, $d[i]$ 用来保存编号为 $i$ 的节点的值(这里节点的值就是这个节点所表示的区间总和),如图所示:![](./images/segt1.png) -图中 $d[1]$ 表示根节点,紫色方框是数组 $a$,红色方框是数组 $d$,红色方框中的括号中的黄色数字表示它所在的那个红色方框表示的线段树节点所表示的区间,如 $d[1]$ 所表示的区间就是 $1\sim 5\ (a[1]\sim a[5])$,即 $d[1]$ 所保存的值是 $a[1]+a[2]+ \cdots +a[5]$,$d[1]=60$ 表示的是 $a[1]+a[2]+ \cdots +a[5]=60$。 +图中 $d[1]$ 表示根节点,紫色方框是数组 $a$ ,红色方框是数组 $d$ ,红色方框中的括号中的黄色数字表示它所在的那个红色方框表示的线段树节点所表示的区间,如 $d[1]$ 所表示的区间就是 $1\sim 5\ (a[1]\sim a[5])$ ,即 $d[1]$ 所保存的值是 $a[1]+a[2]+ \cdots +a[5]$ , $d[1]=60$ 表示的是 $a[1]+a[2]+ \cdots +a[5]=60$ 。 -通过观察我们不难发现,$d[i]$ 的左儿子节点就是 $d[2\times i]$,$d[i]$ 的右节点就是 $d[2\times i+1]$。进一步观察,可以看出如果 $d[i]$ 表示的是区间 $[s,t]$(即 $d[i]=a[s]+a[s+1]+ \cdots +a[t]$ ) 的话,那么 $d[i]$ 的左儿子节点表示的是区间 $[s, \frac{s+t}{2} ]$,$d[i]$ 的右儿子表示的是区间 $[ \frac{s+t}{2} +1,t]$。 +通过观察我们不难发现, $d[i]$ 的左儿子节点就是 $d[2\times i]$ , $d[i]$ 的右节点就是 $d[2\times i+1]$ 。进一步观察,可以看出如果 $d[i]$ 表示的是区间 $[s,t]$ (即 $d[i]=a[s]+a[s+1]+ \cdots +a[t]$ ) 的话,那么 $d[i]$ 的左儿子节点表示的是区间 $[s, \frac{s+t}{2} ]$ , $d[i]$ 的右儿子表示的是区间 $[ \frac{s+t}{2} +1,t]$ 。 -为什么要这样表示呢?因为线段树利用了二分的思想,线段树实际上是个二叉树,这些不懂的话就无法理解线段树了,所以如果不明白二分或者二叉树的话…… 建议去问问度娘。 +为什么要这样表示呢?因为线段树利用了二分的思想,线段树实际上是个二叉树,这些不懂的话就无法理解线段树了,所以如果不明白二分或者二叉树的话……建议去问问度娘。 具体要怎么用代码实现呢? -我们继续观察,有没有发现如果 $d[i]$ 表示的区间大小等于 $1$(区间大小指的是区间包含的元素的个数,即 $a$ 的个数)的话(设 $d[i]$ 表示区间 $[s,t]$,它的区间大小就是 $t-s+1$,不信你看上面的图),那么 $d[i]$ 所表示的区间 $[s,t]$ 中 $s$ 肯定等于 $t$(不信你还是看图),且 $d[i]=a[s]$(当然也等于 $a[t]$)。 +我们继续观察,有没有发现如果 $d[i]$ 表示的区间大小等于 $1$ (区间大小指的是区间包含的元素的个数,即 $a$ 的个数)的话(设 $d[i]$ 表示区间 $[s,t]$ ,它的区间大小就是 $t-s+1$ ,不信你看上面的图),那么 $d[i]$ 所表示的区间 $[s,t]$ 中 $s$ 肯定等于 $t$ (不信你还是看图),且 $d[i]=a[s]$ (当然也等于 $a[t]$ )。 为什么要讲这个东西呢?你没发现这个是个递归边界吗? O(∩\_∩)O 哈哈~ -** 思路如下:** -![](./images/segt2.png) -![](./images/segt3.png) -![](./images/segt4.png) +**思路如下:**![](./images/segt2.png)![](./images/segt3.png)![](./images/segt4.png) 那么就这样写代码: @@ -73,13 +69,13 @@ void build(int s, int t, int p) { 上面那短短 $7$ 行代码就能建立一个线段树。 -关于线段树的空间,如果采用堆式存储(上面的代码就是堆式存储,即 $2\times p$ 是 p 的左儿子,$2 \times p+1$ 是 p 的右儿子),d 数组的大小需要是 $n$ (元素个数)上取到一个 $2$ 的整数次幂(叶子数量)再乘以 $2$(叶子上面的节点数量),上界是 $4$ 倍,可利用上面的 build 自行验证,如果采用动态开点,则需要两倍的空间(需要额外地记录左儿子和右儿子的编号 / 地址)。 +关于线段树的空间,如果采用堆式存储(上面的代码就是堆式存储,即 $2\times p$ 是 p 的左儿子, $2 \times p+1$ 是 p 的右儿子),d 数组的大小需要是 $n$ (元素个数)上取到一个 $2$ 的整数次幂(叶子数量)再乘以 $2$ (叶子上面的节点数量),上界是 $4$ 倍,可利用上面的 build 自行验证,如果采用动态开点,则需要两倍的空间(需要额外地记录左儿子和右儿子的编号/地址)。 ![](./images/segt5.png) ### 线段树的区间查询 -区间查询,比如求区间 $[l,r]$ 的总和(即 $a[l]+a[l+1]+ \cdots +a[r]$)、求区间最大值 / 最小值…… 还有很多很多…… 怎么做呢? +区间查询,比如求区间 $[l,r]$ 的总和(即 $a[l]+a[l+1]+ \cdots +a[r]$ )、求区间最大值/最小值……还有很多很多……怎么做呢? ![](./images/segt6.png) @@ -88,13 +84,13 @@ void build(int s, int t, int p) { ![](./images/segt7.png) (发博客累死了无聊一下) -如果要查询区间 $[1,5]$ 的和,那直接获取 $d[1]$ 的值($60$)即可。那如果我就不查询区间 $[1,5]$,我就查区间 $[3,5]$ 呢? +如果要查询区间 $[1,5]$ 的和,那直接获取 $d[1]$ 的值( $60$ )即可。那如果我就不查询区间 $[1,5]$ ,我就查区间 $[3,5]$ 呢? **Σ(⊙▽⊙"a** 懵 B 了吧。但其实呢我们肯定还是有办法的! -**<( ̄ˇ ̄)/** +**<(~ˇ~)/** 你要查的不是 $[3,5]$ 吗?我把 $[3,5]$ 拆成 $[3,3]$ 和 $[4,5]$ 不就行了吗? @@ -143,19 +139,19 @@ int getsum(int l, int r, int s, int t, int p) { ### 线段树的区间修改与懒惰标记 -区间修改是个很有趣的东西 o(╯□╰)o…… 你想啊,如果你要修改区间 $[l,r]$,难道把所有包含在区间 [l,r] 中的节点都遍历一次、修改一次?那估计这时间复杂度估计会上天 |(\*′口 \`)。这怎么办呢?我们这里要引用一个叫做 **「懒惰标记」** 的东西。 +区间修改是个很有趣的东西 o(╯□╰)o……你想啊,如果你要修改区间 $[l,r]$ ,难道把所有包含在区间[l,r]中的节点都遍历一次、修改一次?那估计这时间复杂度估计会上天 |(\*′口\`)。这怎么办呢?我们这里要引用一个叫做**「懒惰标记」**的东西。 -我们设一个数组 $b$,$b[i]$ 表示编号为 $i$ 的节点的懒惰标记值。啥是懒惰标记、懒惰标记值呢?(O_O)? 这里我再举个栗子(原创小故事我真有才哈哈哈 (◡ᴗ◡✿)): +我们设一个数组 $b$ , $b[i]$ 表示编号为 $i$ 的节点的懒惰标记值。啥是懒惰标记、懒惰标记值呢?(O_O)? 这里我再举个栗子(原创小故事我真有才哈哈哈 (◡ᴗ◡✿)): > A 有两个儿子,一个是 B,一个是 C。 > -> 有一天 A 要建一个新房子,没钱。刚好过年嘛,有人要给 B 和 C 红包,两个红包的钱数相同都是 $(1000000000000001\bmod 2)$ 圆(好多啊!…… 不就是 $1$ 元吗……),然而因为 A 是父亲所以红包肯定是先塞给 A 咯~ +> 有一天 A 要建一个新房子,没钱。刚好过年嘛,有人要给 B 和 C 红包,两个红包的钱数相同都是 $(1000000000000001\bmod 2)$ 圆(好多啊!……不就是 $1$ 元吗……),然而因为 A 是父亲所以红包肯定是先塞给 A 咯~ > -> 理论上来讲 A 应该把两个红包分别给 B 和 C,但是…… 缺钱嘛,A 就把红包偷偷收到自己口袋里了。 +> 理论上来讲 A 应该把两个红包分别给 B 和 C,但是……缺钱嘛,A 就把红包偷偷收到自己口袋里了。 > > A 高兴♂地说:「我现在有 $2$ 份红包了!我又多了 $2\times (1000000000000001\bmod 2)=2$ 圆了!哈哈哈~」 > -> 但是 A 知道,如果他不把红包给 B 和 C,那 B 和 C 肯定会不爽然后导致家庭矛盾最后崩溃,所以 A 对儿子 B 和 C 说:「我欠你们每人 $1$ 份 $(1000000000000001\bmod 2)$ 圆的红包,下次有新红包给过来的时候再给你们!这里我先做下记录…… 嗯…… 我钱你们各 $(1000000000000001\bmod 2)$ 圆……」 +> 但是 A 知道,如果他不把红包给 B 和 C,那 B 和 C 肯定会不爽然后导致家庭矛盾最后崩溃,所以 A 对儿子 B 和 C 说:「我欠你们每人 $1$ 份 $(1000000000000001\bmod 2)$ 圆的红包,下次有新红包给过来的时候再给你们!这里我先做下记录……嗯……我钱你们各 $(1000000000000001\bmod 2)$ 圆……」 > > 儿子 B、C 有点恼怒:「可是如果有同学问起我们我们收到了多少红包咋办?你把我们的红包都收了,我们还怎么装 ×?」 > @@ -163,9 +159,9 @@ int getsum(int l, int r, int s, int t, int p) { > > 这样 B、C 才放了心。 > -> 注:$\bmod$ 是取余数的意思,$a\bmod b$ 就是 $a$ 除以 $b$ 的余数,所以……$1000000000000001\bmod 2=1$。 +> 注: $\bmod$ 是取余数的意思, $a\bmod b$ 就是 $a$ 除以 $b$ 的余数,所以…… $1000000000000001\bmod 2=1$ 。 -在这个故事中我们不难看出,A 就是父亲节点,B 和 C 是 A 的儿子节点,而且 B 和 C 是叶子节点,分别对应一个数组中的值(就是之前讲的数组 $a$),我们假设节点 A 表示区间 $[1,2]$(即 $a[1]+a[2]$),节点 B 表示区间 $[1,1]$(即 $a[1]$),节点 C 表示区间 $[2,2]$(即 $a[2]$),它们的初始值都为 $0$(现在才刚开始呢,还没拿到红包,所以都没钱~)。 +在这个故事中我们不难看出,A 就是父亲节点,B 和 C 是 A 的儿子节点,而且 B 和 C 是叶子节点,分别对应一个数组中的值(就是之前讲的数组 $a$ ),我们假设节点 A 表示区间 $[1,2]$ (即 $a[1]+a[2]$ ),节点 B 表示区间 $[1,1]$ (即 $a[1]$ ),节点 C 表示区间 $[2,2]$ (即 $a[2]$ ),它们的初始值都为 $0$ (现在才刚开始呢,还没拿到红包,所以都没钱~)。 如图: @@ -180,9 +176,9 @@ int getsum(int l, int r, int s, int t, int p) { ![](./images/segt12.png) 注:这里 D 表示当前节点的值(即所表示区间的区间和)。 -为什么节点 A 的 D 是 $2\times (1000000000000001\bmod 2)$ 呢?原因很简单。节点 A 表示的区间是 $[1,2]$,一共包含 $2$ 个元素。我们是让 $[1,2]$ 这个区间的每个元素都加上 $1000000000000001\bmod 2$,所以节点 A 的值就加上了 $2\times (1000000000000001\bmod 2)$ 咯 = ̄ω ̄= 。 +为什么节点 A 的 D 是 $2\times (1000000000000001\bmod 2)$ 呢?原因很简单。节点 A 表示的区间是 $[1,2]$ ,一共包含 $2$ 个元素。我们是让 $[1,2]$ 这个区间的每个元素都加上 $1000000000000001\bmod 2$ ,所以节点 A 的值就加上了 $2\times (1000000000000001\bmod 2)$ 咯 =~ω~=。 -如果这时候我们要查询区间 $[1,1]$(即节点 B 的值)怎么办呢?不是说了吗?如果 B 要用到的时候,A 就把它欠的还给 B! +如果这时候我们要查询区间 $[1,1]$ (即节点 B 的值)怎么办呢?不是说了吗?如果 B 要用到的时候,A 就把它欠的还给 B! 具体是这样操作(如图): @@ -253,7 +249,7 @@ int getsum(int l, int r, int s, int t, 你有没有发现区间查询和区间修改很像吗?(...^\_\_^...) -嘻嘻…… 其实平时我打线段树区间修改和查询我都是打一份,另一份复制黏贴以后再稍作修改就行了。 +嘻嘻……其实平时我打线段树区间修改和查询我都是打一份,另一份复制黏贴以后再稍作修改就行了。 如果你是要实现区间修改为某一个值而不是加上某一个值的话,很简单,把上面的代码中所有的 `+=` 替换成 `=` 即可(除了 `sum+=getsum(l,r,m+1,t,p*2+1)` 这一句)。代码如下: @@ -292,9 +288,9 @@ int getsum(int l, int r, int s, int t, int p) { 这里我总结几个线段树的优化: -- $a\times 2$ 可以用 $a<<1$ 代替,$a\div 2$ 可以用 $a>>1$ 代替($<<1$ 和 $\times 2$ 的速度是一样的,即使不开 O2,但 $>>1$ 速度比 $\div 2$ 快)。 +- $a\times 2$ 可以用 $a<<1$ 代替, $a\div 2$ 可以用 $a>>1$ 代替( $<<1$ 和 $\times 2$ 的速度是一样的,即使不开 O2,但 $>>1$ 速度比 $\div 2$ 快)。 - 建树时记录每个节点所对应的区间,就不需要每次计算当前节点的左右端点了,减小代码复杂度。 -- 因为下标为 $a$ 的节点的左儿子下标为 $a\times 2$,右儿子下标为 $a\times 2+1$,所以可以: +- 因为下标为 $a$ 的节点的左儿子下标为 $a\times 2$ ,右儿子下标为 $a\times 2+1$ ,所以可以: ```cpp #define LS(a) (a << 1) @@ -309,9 +305,9 @@ int getsum(int l, int r, int s, int t, int p) { ## 线段树基础题推荐 -### LUOGU P3372 【模板】线段树 1 +### LUOGU P3372【模板】线段树 1 -[传送门 = ̄ω ̄=](https://www.luogu.org/problem/show?pid=3372) +[传送门 =~ω~=](https://www.luogu.org/problem/show?pid=3372) 代码: @@ -372,9 +368,9 @@ int main() { } ``` -### LUOGU P3373 【模板】线段树 2 +### LUOGU P3373【模板】线段树 2 -[传送门 = ̄ω ̄=](https://www.luogu.org/problem/show?pid=3372) +[传送门 =~ω~=](https://www.luogu.org/problem/show?pid=3372) 代码: @@ -514,9 +510,9 @@ int main() { } ``` -### CODEVS 线段树练习 (这是一个系列) +### CODEVS 线段树练习(这是一个系列) -[传送门 = ̄ω ̄=](http://codevs.cn/problem/?q=%E7%BA%BF%E6%AE%B5%E6%A0%91%E7%BB%83%E4%B9%A0) +[传送门 =~ω~=](http://codevs.cn/problem/?q=%E7%BA%BF%E6%AE%B5%E6%A0%91%E7%BB%83%E4%B9%A0) 具体题解去[我的博客](https://www.k-xzy.xyz/)里搜索吧。 @@ -524,7 +520,7 @@ int main() { ### HihoCoder 1078 线段树的区间修改 -[传送门 = ̄ω ̄=](https://cn.vjudge.net/problem/HihoCoder-1078) +[传送门 =~ω~=](https://cn.vjudge.net/problem/HihoCoder-1078) 代码: @@ -603,17 +599,17 @@ int main() { 构造一棵这样的静态线段树需要 $O(nlogn)$ 次合并操作,但是此时的查询复杂度被加速至 $O(1)$ 次合并操作 -在处理线性基这样特殊的信息的时候甚至可以将复杂度降至 $O(nlog^2n)$ +在处理线性基这样特殊的信息的时候甚至可以将复杂度降至 $O(nlog^2n)$ ### 原理 -在查询 $[l,r]$ 这段区间的信息和的时候,将线段树树上代表 $[l,l]$ 的节点和代表 $[r,r]$ 这段区间的节点在线段树上的 lca 求出来, 设这个节点 p 代表的区间为 $[L,R]$,我们会发现一些非常有趣的性质: +在查询 $[l,r]$ 这段区间的信息和的时候,将线段树树上代表 $[l,l]$ 的节点和代表 $[r,r]$ 这段区间的节点在线段树上的 lca 求出来,设这个节点 p 代表的区间为 $[L,R]$ ,我们会发现一些非常有趣的性质: -**1.$[L,R]$ 这个区间一定包含 $[l,r]$** +**1. $[L,R]$ 这个区间一定包含 $[l,r]$ ** 显然,因为它既是 l 的祖先又是 r 的祖先 -**2.$[l,r]$ 这个区间一定跨越 [L,R] 的中点 ** +**2. $[l,r]$ 这个区间一定跨越[L,R]的中点** 由于 p 是 l 和 r 的 lca,这意味着 p 的左儿子是 l 的祖先而不是 r 的祖先,p 的右儿子是 r 的祖先而不是 l 的祖先 @@ -623,17 +619,17 @@ int main() { ### 实现 -具体来讲我们建树的时候对于线段树树上的一个节点,设它代表的区间为 $(l,r]$ +具体来讲我们建树的时候对于线段树树上的一个节点,设它代表的区间为 $(l,r]$ 不同于传统线段树在这个节点里只保留 $[l,r]$ 的和,我们在这个节点里面额外保存 $(l,mid]$ 的后缀和数组和 $(mid,r]$ 的前缀和数组 -这样的话建树的复杂度为 $T(n)=2T(n/2)+O(n)=O(nlogn)$ 同理空间复杂度也从原来的 $O(n)$ 变成了 $O(nlogn)$ +这样的话建树的复杂度为 $T(n)=2T(n/2)+O(n)=O(nlogn)$ 同理空间复杂度也从原来的 $O(n)$ 变成了 $O(nlogn)$ 下面是最关键的询问了~ 如果我们询问的区间是 $[l,r]$ 那么我们把代表 $[l,l]$ 的节点和代表 $[r,r]$ 的节点的 lca 求出来,记为 p -根据刚才的两个性质, l,r 在 p 所包含的区间之内并且一定跨越了 p 的中点 +根据刚才的两个性质,l,r 在 p 所包含的区间之内并且一定跨越了 p 的中点 这意味这一个非常关键的事实是我们可以使用 p 里面的前缀和数组和后缀和数组,将 $[l,r]$ 拆成 $[l,mid]+(mid,r]$ 从而拼出来 $[l,r]$ 这个区间 @@ -641,7 +637,7 @@ int main() { 不过我们好像忽略了点什么? -似乎求 lca 的复杂度似乎还不是 $O(1)$,暴力求是 $O(logn)$ 的,倍增法则是 $O(loglogn)$ 的,转 st 表的代价又太大…… +似乎求 lca 的复杂度似乎还不是 $O(1)$ ,暴力求是 $O(logn)$ 的,倍增法则是 $O(loglogn)$ 的,转 st 表的代价又太大…… ### 堆式建树 @@ -649,7 +645,7 @@ int main() { 此时我们发现线段树上两个节点的 lca 编号,就是两个节点二进制编号的 lcp -lcp 实在是不难求,x 和 y 的二进制下 `lcp=x>>log[x^y]` +lcp 实在是不难求,x 和 y 的二进制下 `lcp=x>>log[x^y]` 所以我们预处理一个 log 数组即可轻松完成求 lca 的工作 diff --git a/docs/ds/sparse-table.md b/docs/ds/sparse-table.md index 56d93c27..7ad8ed96 100644 --- a/docs/ds/sparse-table.md +++ b/docs/ds/sparse-table.md @@ -4,7 +4,7 @@ ### 简介 -RMQ 是英文 Range Maximum / Minimum Query 的缩写,表示区间最大(最小)值。 +RMQ 是英文 Range Maximum/Minimum Query 的缩写,表示区间最大(最小)值。 解决 RMQ 问题的主要方法有两种,分别是 ST 表和线段树。本文主要讲 ST 表。 @@ -20,7 +20,7 @@ RMQ 是英文 Range Maximum / Minimum Query 的缩写,表示区间最大(最 ### ST 表 -$ST$ 表基于倍增思想,可以做到 $O(n\log n)$ 预处理,$O(1)$ 回答每个询问。但是不支持修改操作。 + $ST$ 表基于倍增思想,可以做到 $O(n\log n)$ 预处理, $O(1)$ 回答每个询问。但是不支持修改操作。 暴力跑的慢的原因在于检索了每一个点。 @@ -28,27 +28,27 @@ $ST$ 表基于倍增思想,可以做到 $O(n\log n)$ 预处理,$O(1)$ 回答 令 $f[i][j]$ 表示 $[i,i+2^j-1]$ 的最大值。 -显然,$f[i][0]=a[i]$ +显然, $f[i][0]=a[i]$ -根据定义式,写出状态转移方程:$f[i][j]=\max(f[i][j-1],f[i+2^{j-1}][j-1])$ +根据定义式,写出状态转移方程: $f[i][j]=\max(f[i][j-1],f[i+2^{j-1}][j-1])$ 我们可以这么理解:将区间 $[i,i+2^j-1]$ 分成相同的两部分 -中点即为 $(i+(i+2^j-1))/2=i+2^{j-1}-1/2$ +中点即为 $(i+(i+2^j-1))/2=i+2^{j-1}-1/2$ -所以 $[i,i+2^j-1]$ 可以分成 $[i,i+2^{j-1}-1]$ 和 $[i+2^{j-1}+1,i+2^j-1]$ +所以 $[i,i+2^j-1]$ 可以分成 $[i,i+2^{j-1}-1]$ 和 $[i+2^{j-1}+1,i+2^j-1]$ ![](./images/st1.png) 预处理终于完成了!接下来就是查询了 -对于每个询问 $[x,y]$,我们把它分成两部分 +对于每个询问 $[x,y]$ ,我们把它分成两部分 -$f[x][s]$ $f[y-2^s+1][s]$ + $f[x][s]$ $f[y-2^s+1][s]$ -其中 $s=\log_2{(y-x+1)}$ +其中 $s=\log_2{(y-x+1)}$ -显然,这两个区间会重叠。但是,重叠并不会对区间最大值产生影响。同时这两个区间刚好覆盖了 $[x,y]$,可以保证答案的正确性。 +显然,这两个区间会重叠。但是,重叠并不会对区间最大值产生影响。同时这两个区间刚好覆盖了 $[x,y]$ ,可以保证答案的正确性。 ### 模板代码 @@ -100,7 +100,7 @@ int main() { 1. 输入输出数据一般很多,建议开启输入输出优化 -2. 每次用 [std::log](https://en.cppreference.com/w/cpp/numeric/math/log) 重新计算 log 函数值并不值得,建议如下预处理 +2. 每次用[std::log](https://en.cppreference.com/w/cpp/numeric/math/log)重新计算 log 函数值并不值得,建议如下预处理 $$ \left\{\begin{aligned} @@ -111,7 +111,7 @@ $$ ### 总结 -$ST$ 表能较好的维护区间信息,时间复杂度较低,代码量相对其他算法不大。但是,$ST$ 表能维护的信息非常有限,不能较好地扩展,并且不支持修改操作。 + $ST$ 表能较好的维护区间信息,时间复杂度较低,代码量相对其他算法不大。但是, $ST$ 表能维护的信息非常有限,不能较好地扩展,并且不支持修改操作。 ## 树上倍增求 LCA @@ -119,15 +119,15 @@ $ST$ 表能较好的维护区间信息,时间复杂度较低,代码量相对 LCA(Least Common Ancestors)表示最近公共祖先。 -对于一棵有根树,设 $LCA(u,v)=x$,则 $x$ 必须满足以下条件 +对于一棵有根树,设 $LCA(u,v)=x$ ,则 $x$ 必须满足以下条件 -- $x$ 是 u 的祖先或 u +- $x$ 是 u 的祖先或 u -- $x$ 是 v 的祖先或 v +- $x$ 是 v 的祖先或 v -- $x$ 是在满足上面两个条件下深度最大的 +- $x$ 是在满足上面两个条件下深度最大的 -显然,在一棵有根树内,对于任意两个节点有且仅有一个 $LCA$ +显然,在一棵有根树内,对于任意两个节点有且仅有一个 $LCA$ 解决这个问题,我们通常有以下方法 @@ -143,21 +143,21 @@ LCA(Least Common Ancestors)表示最近公共祖先。 1. 将两个点跳到同一深度 - 将深度大的点 ** 一步一步 ** 往上跳,发现另一个点是他的祖先,则另一个点就是 $LCA$ + 将深度大的点**一步一步**往上跳,发现另一个点是他的祖先,则另一个点就是 $LCA$ 2. 一起往上跳 - 当两个点深度一样但是还没有找到 LCA 的时候,就一起往 ** 一步一步 ** 上跳,知道跳到了同一个点。那么,这个点即为它们的 LCA + 当两个点深度一样但是还没有找到 LCA 的时候,就一起往**一步一步**上跳,知道跳到了同一个点。那么,这个点即为它们的 LCA ### 树上倍增 -暴力慢的原因在于跳的时候是 ** 一步一步 ** 跳的,导致效率较低。如果我们可以 ** 一次跳多步 **,效率就大大提高了。 +暴力慢的原因在于跳的时候是**一步一步**跳的,导致效率较低。如果我们可以**一次跳多步**,效率就大大提高了。 #### 预处理 -令 $f[i][j]$ 表示 $i$ 的 $2^j$ 辈祖先,及从 $i$ 向根节点走 $2^j$ 步到达的节点。$f[i][0]$ 就表示 $i$ 的父节点。 +令 $f[i][j]$ 表示 $i$ 的 $2^j$ 辈祖先,及从 $i$ 向根节点走 $2^j$ 步到达的节点。 $f[i][0]$ 就表示 $i$ 的父节点。 -通过 $2^{j-1}\times 2^{j-1}=2^j$ 可以得到状态转移方程 $f[i][j]=f[f[i][j-1]][j-1]$~~(是不是和 $ST$ 的转移方程有点像)~~。自然,当 $i$ 没有 $2^j$ 辈祖先时 $f[i][j]=0$ +通过 $2^{j-1}\times 2^{j-1}=2^j$ 可以得到状态转移方程 $f[i][j]=f[f[i][j-1]][j-1]$ ~~(是不是和 $ST$ 的转移方程有点像)~~。自然,当 $i$ 没有 $2^j$ 辈祖先时 $f[i][j]=0$ 一遍 DFS 计算即可 @@ -179,13 +179,13 @@ void dfs(int u, int father) { 依然采用暴力的思想。先将两个节点跳到同一深度,然后一起往上跳。 -只不过在跳的过程中从一步一步跳变成了 ** 一次跳多步 **。可以具体分为以下几步 +只不过在跳的过程中从一步一步跳变成了**一次跳多步**。可以具体分为以下几步 1. 让 $x$ 的深度比 $y$ 大(深度在预处理时已经求出) -2. 将两个节点跳到同一深度。在此处我们使用二进制思想,依次尝试向上跳 $2^i,2^{i-1}\cdots 2^1,2^0$。如果发现则 $x$ 跳到了 $y$ 就说明 $LCA(x,y)=y$ +2. 将两个节点跳到同一深度。在此处我们使用二进制思想,依次尝试向上跳 $2^i,2^{i-1}\cdots 2^1,2^0$ 。如果发现则 $x$ 跳到了 $y$ 就说明 $LCA(x,y)=y$ -3. 一起往上跳。依然使用二进制思想,让他们一起往上跳 $2^i,2^{i-1}\cdots 2^1,2^0$. 如果 $f[x][i]!=f[y][i]$,说明 $x$ 和 $y$ 还未相遇。最后,$x$ 和 $y$ 必定只差一步相遇。这时 $x$ 的父亲即 $f[x][0]$ 就是他们的 LCA +3. 一起往上跳。依然使用二进制思想,让他们一起往上跳 $2^i,2^{i-1}\cdots 2^1,2^0$ . 如果 $f[x][i]!=f[y][i]$ ,说明 $x$ 和 $y$ 还未相遇。最后, $x$ 和 $y$ 必定只差一步相遇。这时 $x$ 的父亲即 $f[x][0]$ 就是他们的 LCA ```cpp int lca(int x, int y) { diff --git a/docs/ds/splay.md b/docs/ds/splay.md index 076f1e79..797c2402 100644 --- a/docs/ds/splay.md +++ b/docs/ds/splay.md @@ -4,7 +4,7 @@ ## 简介 -$\text{Splay}$ 是一种二叉查找树,它通过不断将某个节点旋转到根节点,使得整棵树仍然满足二叉查找树的性质,并且保持平衡而不至于退化为链,它由 Daniel Sleator 和 Robert Tarjan 发明。 + $\text{Splay}$ 是一种二叉查找树,它通过不断将某个节点旋转到根节点,使得整棵树仍然满足二叉查找树的性质,并且保持平衡而不至于退化为链,它由 Daniel Sleator 和 Robert Tarjan 发明。 * * * @@ -18,9 +18,9 @@ $\text{Splay}$ 是一种二叉查找树,它通过不断将某个节点旋转 ### 节点维护信息 -| $rt$ | $tot$ | $fa[i]$ | $ch[i][0/1]$ | $val[i]$ | $cnt[i]$ | $sz[i]$ | -| :---- | :---- | :------ | :----------- | :------- | :------- | :------ | -| 根节点编号 | 节点个数 | 父亲 | 左右儿子编号 | 节点权值 | 权值出现次数 | 子树大小 | +| $rt$ | $tot$ | $fa[i]$ | $ch[i][0/1]$ | $val[i]$ | $cnt[i]$ | $sz[i]$ | +| :----- | :------ | :-------- | :------------- | :--------- | :--------- | :-------- | +| 根节点编号 | 节点个数 | 父亲 | 左右儿子编号 | 节点权值 | 权值出现次数 | 子树大小 | * * * @@ -28,9 +28,9 @@ $\text{Splay}$ 是一种二叉查找树,它通过不断将某个节点旋转 ### 基本操作 -- $\text{maintain}(x)$:在改变节点位置前,将节点 $x$ 的 $\text{size}$ 更新。 -- $\text{get}(x)$:判断节点 $x$ 是父亲节点的左儿子还是右儿子。 -- $\text{clear}(x)$:销毁节点 $x$。 +- $\text{maintain}(x)$ :在改变节点位置前,将节点 $x$ 的 $\text{size}$ 更新。 +- $\text{get}(x)$ :判断节点 $x$ 是父亲节点的左儿子还是右儿子。 +- $\text{clear}(x)$ :销毁节点 $x$ 。 ```cpp void maintain(int x) { sz[x] = sz[ch[x][0]] + sz[ch[x][1]] + cnt[x]; } @@ -46,20 +46,17 @@ void clear(int x) { ch[x][0] = ch[x][1] = fa[x] = val[x] = sz[x] = cnt[x] = 0; } - 整棵 $\text{Splay}$ 的中序遍历不变(不能破坏二叉查找树的性质)。 - 受影响的节点维护的信息依然正确有效。 -- $\text{root}$ 必须指向旋转后的根节点。 +- $\text{root}$ 必须指向旋转后的根节点。 在 $\text{Splay}$ 中旋转分为两种:左旋和右旋。 ![](./images/splay2.png) -**具体分析旋转步骤**(假设需要旋转的节点为 $x$,其父亲为 $y$,以右旋为例) +**具体分析旋转步骤**(假设需要旋转的节点为 $x$ ,其父亲为 $y$ ,以右旋为例) -1. 将 $y$ 的左儿子指向 $x$ 的右儿子,且 $x$ 的右儿子的父亲指向 $y$。 - `ch[y][0]=ch[x][1]; fa[ch[x][1]]=y;` -2. 将 $x$ 的右儿子指向 $y$,且 $y$ 的父亲指向 $x$。 - `ch[x][chk^1]=y; fa[y]=x;` -3. 如果原来的 $y$ 还有父亲 $z$,那么把 $z$ 的某个儿子(原来 $y$ 所在的儿子位置)指向 $x$,且 $x$ 的父亲指向 $z$。 - `fa[x]=z; if(z) ch[z][y==ch[z][1]]=x;` +1. 将 $y$ 的左儿子指向 $x$ 的右儿子,且 $x$ 的右儿子的父亲指向 $y$ 。 `ch[y][0]=ch[x][1]; fa[ch[x][1]]=y;` +2. 将 $x$ 的右儿子指向 $y$ ,且 $y$ 的父亲指向 $x$ 。 `ch[x][chk^1]=y; fa[y]=x;` +3. 如果原来的 $y$ 还有父亲 $z$ ,那么把 $z$ 的某个儿子(原来 $y$ 所在的儿子位置)指向 $x$ ,且 $x$ 的父亲指向 $z$ 。 `fa[x]=z; if(z) ch[z][y==ch[z][1]]=x;` ```cpp void rotate(int x) { @@ -77,13 +74,13 @@ void rotate(int x) { ### Splay 操作 -$\text{Splay}$ 规定:每访问一个节点后都要强制将其旋转到根节点。此时旋转操作具体分为 $6$ 种情况讨论(其中 $x$ 为需要旋转到根的节点) + $\text{Splay}$ 规定:每访问一个节点后都要强制将其旋转到根节点。此时旋转操作具体分为 $6$ 种情况讨论(其中 $x$ 为需要旋转到根的节点) ![](./images/splay1.png) -- 如果 $x$ 的父亲是根节点,直接将 $x$ 左旋或右旋(图 $1,2$)。 -- 如果 $x$ 的父亲不是根节点,且 $x$ 和父亲的儿子类型相同,首先将其父亲左旋或右旋,然后将 $x$ 右旋或左旋(图 $3,4$)。 -- 如果 $x$ 的父亲不是根节点,且 $x$ 和父亲的儿子类型不同,将 $x$ 左旋再右旋、或者右旋再左旋(图 $5,6$)。 +- 如果 $x$ 的父亲是根节点,直接将 $x$ 左旋或右旋(图 $1,2$ )。 +- 如果 $x$ 的父亲不是根节点,且 $x$ 和父亲的儿子类型相同,首先将其父亲左旋或右旋,然后将 $x$ 右旋或左旋(图 $3,4$ )。 +- 如果 $x$ 的父亲不是根节点,且 $x$ 和父亲的儿子类型不同,将 $x$ 左旋再右旋、或者右旋再左旋(图 $5,6$ )。 分析起来一大串,其实代码一小段。大家可以自己模拟一下 $6$ 种旋转情况,就能理解 $\text{Splay}$ 的基本思想了。 @@ -97,7 +94,7 @@ void splay(int x) { ### 插入操作 -插入操作是一个比较复杂的过程,具体步骤如下(插入的值为 $k$): +插入操作是一个比较复杂的过程,具体步骤如下(插入的值为 $k$ ): - 如果树空了则直接插入根并退出。 - 如果当前节点的权值等于 $k$ 则增加当前节点的大小并更新节点和父亲的信息,将当前节点进行 $\text{Splay}$ 操作。 @@ -142,7 +139,7 @@ void ins(int k) { 根据二叉查找树的定义和性质,显然可以按照以下步骤查询 $x$ 的排名: - 如果 $x$ 比当前节点的权值小,向其左子树查找。 -- 如果 $x$ 比当前节点的权值大,将答案加上左子树($size$)和当前节点($cnt$)的大小,向其右子树查找。 +- 如果 $x$ 比当前节点的权值大,将答案加上左子树( $size$ )和当前节点( $cnt$ )的大小,向其右子树查找。 - 如果 $x$ 与当前节点的权值相同,将答案加 $1$ 并返回。 注意最后需要进行 $\text{Splay}$ 操作。 @@ -170,8 +167,8 @@ int rk(int k) { 设 $k$ 为剩余排名,具体步骤如下: -- 如果左子树非空且剩余排名 $k$ 不大于左子树的大小 $size$,那么向左子树查找。 -- 否则将 $k$ 减去左子树的和根的大小。如果此时 $k$ 的值小于等于 $0$,则返回根节点的权值,否则继续向右子树查找。 +- 如果左子树非空且剩余排名 $k$ 不大于左子树的大小 $size$ ,那么向左子树查找。 +- 否则将 $k$ 减去左子树的和根的大小。如果此时 $k$ 的值小于等于 $0$ ,则返回根节点的权值,否则继续向右子树查找。 ```cpp int kth(int k) { @@ -202,7 +199,7 @@ int pre() { ### 查询后继 -后继定义为大于 $x$ 的最小的数,查询方法和前驱类似:$x$ 的右子树中最左边的节点。 +后继定义为大于 $x$ 的最小的数,查询方法和前驱类似: $x$ 的右子树中最左边的节点。 ```cpp int nxt() { @@ -219,7 +216,7 @@ int nxt() { - 首先将 $x$ 旋转到根的位置。 - 接下来分为多个情况考虑: -1. 如果有不止一个 $x$,那么将 $cnt[x]$ 减 $1$ 并退出。 +1. 如果有不止一个 $x$ ,那么将 $cnt[x]$ 减 $1$ 并退出。 2. 如果 $x$ 没有儿子节点,那么直接将当前节点 $\text{clear}$ 并退出。 3. 如果 $x$ 只有一个儿子,那么先将当前节点 $\text{clear}$ 再把唯一的儿子作为根节点。 4. 否则将 $x$ 的前驱旋转到根并作为根节点,将 $x$ 的右子树接到根节点的右子树上,最后要将根的信息更新。 @@ -422,19 +419,19 @@ int main() { 以下题目都是裸的 $\text{Splay}$ 维护二叉查找树。~~(直接套板子即可)~~ - [【模板】普通平衡树](https://www.luogu.org/problemnew/show/P3369) -- [\[HNOI2002\] 营业额统计](https://www.lydsy.com/JudgeOnline/problem.php?id=1588) -- [\[HNOI2004\] 宠物收养所](https://www.lydsy.com/JudgeOnline/problem.php?id=1208) +- [\[HNOI2002\]营业额统计](https://www.lydsy.com/JudgeOnline/problem.php?id=1588) +- [\[HNOI2004\]宠物收养所](https://www.lydsy.com/JudgeOnline/problem.php?id=1208) ## 练习题 -[bzoj 1552 \[Cerc2007\] robotic sort](https://www.lydsy.com/JudgeOnline/problem.php?id=1552) (权限题) +[bzoj 1552\[Cerc2007\]robotic sort](https://www.lydsy.com/JudgeOnline/problem.php?id=1552)(权限题) -[luogu P3380 【模板】二逼平衡树(树套树)](https://www.luogu.org/problemnew/show/P3380) +[luogu P3380【模板】二逼平衡树(树套树)](https://www.luogu.org/problemnew/show/P3380) [bzoj 2827 千山鸟飞绝](https://www.lydsy.com/JudgeOnline/problem.php?id=2827) -[bzoj 4923 \[Lydsy1706 月赛\]K 小值查询](https://www.lydsy.com/JudgeOnline/problem.php?id=4923) +[bzoj 4923\[Lydsy1706 月赛\]K 小值查询](https://www.lydsy.com/JudgeOnline/problem.php?id=4923) * * * -> 本文部分内容引用于 [algocode 算法博客](https://algocode.net),特别鸣谢! +> 本文部分内容引用于[algocode 算法博客](https://algocode.net),特别鸣谢! diff --git a/docs/ds/square-root-decomposition.md b/docs/ds/square-root-decomposition.md index 8aad6c29..ad61b2cc 100644 --- a/docs/ds/square-root-decomposition.md +++ b/docs/ds/square-root-decomposition.md @@ -4,7 +4,7 @@ 从 NOIP 到 NOI 到 IOI,各种难度的分块思想都有出现。 -通常的分块算法的复杂度带根号,或者其他奇怪的复杂度,而不是 $\log$。 +通常的分块算法的复杂度带根号,或者其他奇怪的复杂度,而不是 $\log$ 。 分块是一种很灵活的思想,几乎什么都能分块,并且不难实现。 @@ -20,7 +20,7 @@ 动机:线段树太难写? -将序列分段,每段长度 $T$,那么一共有 $\frac{n}{T}$ 段。 +将序列分段,每段长度 $T$ ,那么一共有 $\frac{n}{T}$ 段。 维护每一段的区间和。 @@ -30,15 +30,15 @@ 完整段使用维护的信息,一部分暴力求。 -复杂度 $O(\frac{n}{T}+T)$。 +复杂度 $O(\frac{n}{T}+T)$ 。 区间修改:同样涉及这些东西,使用打标记和暴力修改,同样的复杂度。 -当 $T=\sqrt{n}$ 时,复杂度 $O(\sqrt{n})$。 +当 $T=\sqrt{n}$ 时,复杂度 $O(\sqrt{n})$ 。 ## 区间和 2 -上一个做法的复杂度是 $\Omega(1) , O(\sqrt{n})$。 +上一个做法的复杂度是 $\Omega(1) , O(\sqrt{n})$ 。 我们在这里介绍一种 $O(\sqrt{n}) - O(1)$ 的算法。 @@ -48,20 +48,20 @@ 以及整块作为一个单位的前缀和。 -每次修改 $O(T+\frac{n}{T})$。 +每次修改 $O(T+\frac{n}{T})$ 。 -询问:涉及三部分,每部分都可以直接通过前缀和得到,时间复杂度 $O(1)$。 +询问:涉及三部分,每部分都可以直接通过前缀和得到,时间复杂度 $O(1)$ 。 ## 对询问分块 -同样的问题,现在序列长度为 $n$,有 $m$ 个操作。 +同样的问题,现在序列长度为 $n$ ,有 $m$ 个操作。 如果操作数量比较少,我们可以把操作记下来,在询问的时候加上这些操作的影响。 -假设最多记录 $T$ 个操作,则修改 $O(1)$,询问 $O(T)$。 +假设最多记录 $T$ 个操作,则修改 $O(1)$ ,询问 $O(T)$ 。 -$T$ 个操作之后,重新计算前缀和,$O(n)$。 + $T$ 个操作之后,重新计算前缀和, $O(n)$ 。 -总复杂度:$O(mT+n\frac{m}{T})$。 +总复杂度: $O(mT+n\frac{m}{T})$ 。 -$T=\sqrt{n}$ 时,总复杂度 $O(m \sqrt{n})$。 + $T=\sqrt{n}$ 时,总复杂度 $O(m \sqrt{n})$ 。 diff --git a/docs/ds/stack.md b/docs/ds/stack.md index 40b04ad7..4dc2827d 100644 --- a/docs/ds/stack.md +++ b/docs/ds/stack.md @@ -1,13 +1,13 @@ ## 栈 -栈是 OI 中常用的一种线性数据结构,请注意,本文主要讲的是栈这种数据结构, 而非程序运行时的系统栈 / 栈空间 +栈是 OI 中常用的一种线性数据结构,请注意,本文主要讲的是栈这种数据结构,而非程序运行时的系统栈/栈空间 栈的修改是按照后进先出的原则进行的,因此栈通常被称为是后进先出(last in first out)表,简称 LIFO 表。 !!! warning 为什么不是 FILO 呢? -我们可以方便的使用数组来模拟一个栈, 如下 : +我们可以方便的使用数组来模拟一个栈,如下: ```cpp int stk[N]; @@ -22,7 +22,7 @@ if (*stk) --*stk; *stk = 0; ``` -同时 STL 也提供了一个方法 `std :: stack` +同时 STL 也提供了一个方法 `std :: stack` ```cpp #include @@ -38,24 +38,24 @@ if (*stk) --*stk; */ ``` -`std :: stack` 支持赋值运算符 `=` + `std :: stack` 支持赋值运算符 `=` -元素访问 : +元素访问: -`s.top()` 返回栈顶 + `s.top()` 返回栈顶 -容量 : +容量: -`s.empty()` 返回是否为空 + `s.empty()` 返回是否为空 -`s.size()` 返回元素数量 + `s.size()` 返回元素数量 -修改 : +修改: -`s.push()` 插入传入的参数到栈顶 + `s.push()` 插入传入的参数到栈顶 -`s.pop()` 弹出栈顶 + `s.pop()` 弹出栈顶 -其他运算符 : +其他运算符: -`==`、`!=`、`<`、`<=`、`>`、`>=` 可以按照字典序比较两个 `stack` 的值 + `==` 、 `!=` 、 `<` 、 `<=` 、 `>` 、 `>=` 可以按照字典序比较两个 `stack` 的值 diff --git a/docs/ds/stl.md b/docs/ds/stl.md index ff532fdd..0a310ff0 100644 --- a/docs/ds/stl.md +++ b/docs/ds/stl.md @@ -4,46 +4,46 @@ STL 是 Standard Template Library 的简称,中文名为标准模板库。它是 C++ 的一大特色,里面包含了许多标准算法或数据结构。 -在 C++ 标准中,STL 被组织为下面的 13 个头文件:``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``。 +在 C++ 标准中,STL 被组织为下面的 13 个头文件: `` , `` , `` , `` , `` , `` , `` , `` , `` , `` , `` , `` , `` , `` , `` , `` 。 ## 数据结构 ### 序列式容器 -**向量** (vector) 连续存储的元素。 +**向量**(vector) 连续存储的元素。 -**列表** (list) 由节点组成的双向链表,每个结点包含着一个元素。 +**列表**(list) 由节点组成的双向链表,每个结点包含着一个元素。 -**双端队列** (deque) 连续存储的指向不同元素的指针所组成的数组。 +**双端队列**(deque) 连续存储的指向不同元素的指针所组成的数组。 ### 适配器容器 -**栈** (stack) 后进先出的值的排列 。 +**栈**(stack) 后进先出的值的排列。 -**队列** (queue) 先进先出的值的排列 。 +**队列**(queue) 先进先出的值的排列。 -**优先队列** (priority_queue) 元素的次序是由作用于所存储的值对上的某种谓词决定的的一种队列 。 +**优先队列**(priority_queue) 元素的次序是由作用于所存储的值对上的某种谓词决定的的一种队列。 ### 关联式容器 -**集合** (set) 由节点组成的红黑树,每个节点都包含着一个元素,节点之间以某种作用于元素对的谓词排列,没有两个不同的元素能够拥有相同的次序 。 +**集合**(set) 由节点组成的红黑树,每个节点都包含着一个元素,节点之间以某种作用于元素对的谓词排列,没有两个不同的元素能够拥有相同的次序。 -**多重集合** (multiset) 允许存在两个次序相等的元素的集合 。 +**多重集合**(multiset) 允许存在两个次序相等的元素的集合。 -**映射** (map) 由 {键,值} 对组成的集合,以某种作用于键对上的谓词排列 。 +**映射**(map) 由 {键,值} 对组成的集合,以某种作用于键对上的谓词排列。 -**多重映射** (multimap) 允许键对有相等的次序的映射 。 +**多重映射**(multimap) 允许键对有相等的次序的映射。 ## 算法 -STL 提供了大约 100 个实现算法的模版函数,基本都包含在 `` 之中,还有一部分包含在 `` 和 ``。 +STL 提供了大约 100 个实现算法的模版函数,基本都包含在 `` 之中,还有一部分包含在 `` 和 `` 。 常用函数: -- `sort`:排序。`sort(v.begin(), v.end(), cmp)` 或 `sort(a + begin, a + end, cmp)`,其中 `end` 是排序的数组最后一个元素的后一位,`cmp` 为自定义的比较函数。 -- `reverse`:翻转数组、字符串。`reverse(v.begin(), v.end())` 或 `reverse(a + begin, a + end)`。 -- `nth_element`:按指定范围进行分类,即找出序列中第 $n$ 大的元素,使其左边均为小于它的数,右边均为大于它的数。`nth_element(v.begin(), v.begin() + mid, v.end(), cmp)`或 `nth_element(a + begin, a + begin + mid, a + end, cmp)`。 -- `random_shuffle`:随机地打乱数组。`random_shuffle(v.begin(), v.end())` 或 `random_shuffle(v + begin, v + end)`。 +- `sort` :排序。 `sort(v.begin(), v.end(), cmp)` 或 `sort(a + begin, a + end, cmp)` ,其中 `end` 是排序的数组最后一个元素的后一位, `cmp` 为自定义的比较函数。 +- `reverse` :翻转数组、字符串。 `reverse(v.begin(), v.end())` 或 `reverse(a + begin, a + end)` 。 +- `nth_element` :按指定范围进行分类,即找出序列中第 $n$ 大的元素,使其左边均为小于它的数,右边均为大于它的数。 `nth_element(v.begin(), v.begin() + mid, v.end(), cmp)` 或 `nth_element(a + begin, a + begin + mid, a + end, cmp)` 。 +- `random_shuffle` :随机地打乱数组。 `random_shuffle(v.begin(), v.end())` 或 `random_shuffle(v + begin, v + end)` 。 ## 参考 diff --git a/docs/ds/stl/bitset.md b/docs/ds/stl/bitset.md index c14f35dc..05aaeead 100644 --- a/docs/ds/stl/bitset.md +++ b/docs/ds/stl/bitset.md @@ -1,24 +1,24 @@ ## 介绍 -`std :: bitset`是标准库中的一个**固定大小**序列, 其储存的数据只包含`0/1` + `std :: bitset` 是标准库中的一个**固定大小**序列,其储存的数据只包含 `0/1` -众所周知, 由于内存地址是按字节即`byte`寻址, 而非比特`bit`, +众所周知,由于内存地址是按字节即 `byte` 寻址,而非比特 `bit` , -我们一个`bool`类型的变量, 虽然只能表示`0/1`, 但是也占了`1byte`的内存 +我们一个 `bool` 类型的变量,虽然只能表示 `0/1` , 但是也占了 `1byte` 的内存 -`bitset`就是通过固定的优化, 使得一个字节的八个比特能分别储存 8 位的`0/1` + `bitset` 就是通过固定的优化,使得一个字节的八个比特能分别储存 8 位的 `0/1` -对于一个 4 字节的`int`变量, 在只存`0/1`的意义下, `bitset`占用空间只是其 $\frac{1}{32}$ +对于一个 4 字节的 `int` 变量,在只存 `0/1` 的意义下, `bitset` 占用空间只是其 $\frac{1}{32}$ -在某些情况下通过`bitset`可以使你的复杂度除以 32 +在某些情况下通过 `bitset` 可以使你的复杂度除以 32 -当然, `vector`的一个特化`vector`的储存方式同`bitset`一样, 区别在于其支持动态开空间, +当然, `vector` 的一个特化 `vector` 的储存方式同 `bitset` 一样,区别在于其支持动态开空间, -`bitset`则和我们一般的静态数组一样, 是在编译时就开好了的. + `bitset` 则和我们一般的静态数组一样,是在编译时就开好了的。 -那么为什么要用`bitset`而非`vector` ? +那么为什么要用 `bitset` 而非 `vector` ? -通过以下的介绍, 你可以更加详细的看到`bitset`具备的方便操作 +通过以下的介绍,你可以更加详细的看到 `bitset` 具备的方便操作 ```cpp #include // 包含 bitset 的头文件 @@ -26,33 +26,33 @@ ### 运算符 -- `operator[]`: 访问其特定的一位 +- `operator[]` : 访问其特定的一位 -- `operator ==/!=` : 比较两个`bitset`内容是否完全一样 +- `operator ==/!=` : 比较两个 `bitset` 内容是否完全一样 -- `operator &= / |= / ^= / ~` : 进行按位与 / 或 / 异或 / 取反操作 -- `operator <> / <<= / >>=` : 进行二进制左移 / 右移 -- `operator <>` : 流运算符, 这意味着你可以通过`cin/cout`进行输入输出 +- `operator &=/|=/^=/~` : 进行按位与/或/异或/取反操作 +- `operator <>/<<=/>>=` : 进行二进制左移/右移 +- `operator <>` : 流运算符,这意味着你可以通过 `cin/cout` 进行输入输出 -`vector`只具有前两项 + `vector` 只具有前两项 ### 成员函数 -- `test()`: 它和`vector`中的`at()`的作用是一样的, 和`[]`运算符的区别就是越界检查 -- `count()`: 返回`true`的数量 -- `set()`: 将整个`bitset`设置成`true`, 你也可以传入参数使其设置成你的参数 -- `reset()`: 将整个`bitset`设置成`false` -- `flip()`: 翻转该位 (0 变 1,1 变 0), 相当于逻辑非 / 异或 1 -- `to_string()`: 返回转换成的字符串表达 -- `to_ulong()`: 返回转换成的`unsigned long`表达 (`long`在 NT 及 32 位 POSIX 系统下与`int`一样, 在 64 位 POSIX 下与`long long`一样) -- `to_ullong()` **C++11**, 返回转换成的`unsigned long long`表达 +- `test()` : 它和 `vector` 中的 `at()` 的作用是一样的,和 `[]` 运算符的区别就是越界检查 +- `count()` : 返回 `true` 的数量 +- `set()` : 将整个 `bitset` 设置成 `true` , 你也可以传入参数使其设置成你的参数 +- `reset()` : 将整个 `bitset` 设置成 `false` +- `flip()` : 翻转该位 (0 变 1,1 变 0), 相当于逻辑非/异或 1 +- `to_string()` : 返回转换成的字符串表达 +- `to_ulong()` : 返回转换成的 `unsigned long` 表达 ( `long` 在 NT 及 32 位 POSIX 系统下与 `int` 一样,在 64 位 POSIX 下与 `long long` 一样) +- `to_ullong()` **C++11**, 返回转换成的 `unsigned long long` 表达 -这些`vector`基本都没有 +这些 `vector` 基本都没有 ## 作用 -一般来讲, 我们可以用`bitset`优化一些可行性 DP, 或者线筛素数 (`notprime`这种`bool`数组可以用`bitset`开到$10^8$之类的) +一般来讲,我们可以用 `bitset` 优化一些可行性 DP, 或者线筛素数 ( `notprime` 这种 `bool` 数组可以用 `bitset` 开到 $10^8$ 之类的) -它最主要的作用还是压掉了内存带来的时间优化, $\frac{1}{32}$的常数优化已经可以是复杂度级别的优化了, 比如一个$N = 1000$的$O(N^3)$算法, $10^9$显然很卡, 在常数大一点的情况下必然卡不过去,**O(松) 不能算!!!!**, 这时候如果我们某一维除以 32, 则可以比较保险的过了这道题 +它最主要的作用还是压掉了内存带来的时间优化, $\frac{1}{32}$ 的常数优化已经可以是复杂度级别的优化了,比如一个 $N = 1000$ 的 $O(N^3)$ 算法, $10^9$ 显然很卡,在常数大一点的情况下必然卡不过去,**O(松)不能算!!!!**, 这时候如果我们某一维除以 32, 则可以比较保险的过了这道题 -其实`bitset`不光是一个容器, 更是一种思想, 我们可以通过手写的方式, 来把`long long`什么的压成每 bit 表示一个信息, 用 STL 的原因更多是因为它的运算符方便 +其实 `bitset` 不光是一个容器,更是一种思想,我们可以通过手写的方式,来把 `long long` 什么的压成每 bit 表示一个信息,用 STL 的原因更多是因为它的运算符方便 diff --git a/docs/ds/stl/map.md b/docs/ds/stl/map.md index b060ad0b..9761e6f5 100644 --- a/docs/ds/stl/map.md +++ b/docs/ds/stl/map.md @@ -1,17 +1,17 @@ -### `map` 是啥鬼? +### `map` 是啥鬼? -`map` 是利用红黑树实现的。 + `map` 是利用红黑树实现的。 -当你在写程序的时候,可能需要存储一些信息,例如存储学生姓名对应的分数,例如:`Tom 0`,`Bob 100`,`Alan 100`。 +当你在写程序的时候,可能需要存储一些信息,例如存储学生姓名对应的分数,例如: `Tom 0` , `Bob 100` , `Alan 100` 。 但是由于数组下标只能为非负整数,所以无法用姓名来存储,这个时候最简单的办法就是使用 STL 的 `map` 了! -`map` 可任意类型为下标(在 `map` 中叫做 `key`,也就是索引),下面是 `map` 的模型: + `map` 可任意类型为下标(在 `map` 中叫做 `key` ,也就是索引),下面是 `map` 的模型: ```cpp map<类型名, 类型名> 你想给map起的名字 ``` -其中两个类型名第一个是 `key`(索引,可以理解为数组的下标),第二个是 `value`(对应的元素)。例如上面的例子,我们可以这样的存储: +其中两个类型名第一个是 `key` (索引,可以理解为数组的下标),第二个是 `value` (对应的元素)。例如上面的例子,我们可以这样的存储: ```cpp map mp @@ -19,29 +19,29 @@ map mp 是不是感觉很神奇? -### `map` 具体怎么使用? +### `map` 具体怎么使用? -- `map` 添加元素 +- `map` 添加元素 -1. 直接存,例如 `mp["Tom"]=0` +1. 直接存,例如 `mp["Tom"]=0` -2. 通过插入,例如 `mp.insert(pair("Alan",100));` +2. 通过插入,例如 `mp.insert(pair("Alan",100));` -3. 初始化( C++11 及以上)和数组差不多: +3. 初始化(C++11 及以上)和数组差不多: ```cpp map mp = {{"Tom", 0}, {"Bob", "100"}, {"Alan", 100}}; ``` -- `map` 查找删除元素 +- `map` 查找删除元素 -1. 在你知道查找元素是啥的时候直接来就可以了,例如:`int grade=mp["Tom"]` +1. 在你知道查找元素是啥的时候直接来就可以了,例如: `int grade=mp["Tom"]` 2. 如果你知道了元素的下标,但是想知道这个元素是否已经存在 `map` 中,可以使用 `find` 函数。 -格式:`if(mp.find()==mp.end())`,意思是是否返回的是 `map` 的末尾,因为 `map` 如果没有查找到元素,迭代器会返回末尾。 +格式: `if(mp.find()==mp.end())` ,意思是是否返回的是 `map` 的末尾,因为 `map` 如果没有查找到元素,迭代器会返回末尾。 -其中 `mp.end()` 返回指向 map 尾部的迭代器, 另外 也可以用 `mp.count(__key) != 0` 来判断 +其中 `mp.end()` 返回指向 map 尾部的迭代器,另外 也可以用 `mp.count(__key) != 0` 来判断 3. 如果你想知道 map 里全部的元素,那么最正确的做法使用迭代器了,如果你还不会,请查阅之前文章中的迭代器。 @@ -51,7 +51,7 @@ for (iter = mp.begin(); iter != mp.end(); iter++) ``` 其中 `mp.begin()` 返回指向 `map` 头部的迭代器 -当然,如果使用 C++11 (及以上)你还可以使用 C++11 的新特性 ,如下 +当然,如果使用 C++11(及以上)你还可以使用 C++11 的新特性,如下 ```cpp for (auto &i : mp) { @@ -59,7 +59,7 @@ for (auto &i : mp) { } ``` -`iter->first` 是 `key` 索引,例如 `Tom`,而 `iter->second` 是 `value`。 + `iter->first` 是 `key` 索引,例如 `Tom` ,而 `iter->second` 是 `value` 。 如果你想删除 `Tom` 这个元素,则可以利用 `find` 函数找到 `Tom` ,然后再 `erase` 如下 @@ -69,34 +69,33 @@ it = mp.find("Tom"); mp.erase(it) ``` -如果你想清空所有的元素,可以直接 `mp.clear()` +如果你想清空所有的元素,可以直接 `mp.clear()` - 其他 我们刚才介绍了最常用的,下面是其他比较常用的: -- `count()` 返回指定元素出现的次数 ,例如 `mp.count()` +- `count()` 返回指定元素出现的次数,例如 `mp.count()` -- `swap()` 可以交换两个 `map` ,例如 `swap(m1,m2)` +- `swap()` 可以交换两个 `map` ,例如 `swap(m1,m2)` -- `size()` 返回 `map` 中元素的个数 - -- `empty()` 如果 `map` 为空则返回 `true`,例如 `mp.empty()`。 +- `size()` 返回 `map` 中元素的个数 +- `empty()` 如果 `map` 为空则返回 `true` ,例如 `mp.empty()` 。 -### `map` 常数靠得住吗? +### `map` 常数靠得住吗? -一般情况下是可以的。无论查询,插入,删除的复杂度都是 $O(\log N)$,遍历是 $O(N)$。 +一般情况下是可以的。无论查询,插入,删除的复杂度都是 $O(\log N)$ ,遍历是 $O(N)$ 。 不过有的时候不会满足啊!我只想查询元素,插入元素,但是时间不够咋办?请往下看! - 由于 NOIP 不资瓷吸氧(开启 O2 优化),所以 NOIP 要注意是否会被卡 -### 更快:基于 `Hash` 实现的 `map`! +### 更快:基于 `Hash` 实现的 `map` ! !!! note - C++11 及以后使用 `std::unordered_map`,在 `` 头文件中 - 之前的版本可以使用 `std::tr1::unordered_map`,在 `` 头文件中 + C++11 及以后使用 `std::unordered_map` ,在 `` 头文件中 + 之前的版本可以使用 `std::tr1::unordered_map` ,在 `` 头文件中 -这个 `map` 的名字就是 `unordered_map` 了,它的查询,插入,删除的复杂度几乎是 $O(1)$ 级别(所有的操作几乎和 `map`一样(注意 `unordered_map` 用迭代器遍历是无序的)。 +这个 `map` 的名字就是 `unordered_map` 了,它的查询,插入,删除的复杂度几乎是 $O(1)$ 级别(所有的操作几乎和 `map` 一样(注意 `unordered_map` 用迭代器遍历是无序的)。 -但是在最坏情况下(产生大量 hash 冲突时),`unordered_map`的各项操作的时间复杂度可达$O(n^2)$ 。[ (详情见 Codeforces 上发表的一篇卡 unordered_map 的文章) ](http://codeforces.com/blog/entry/62393) 而且它的遍历速度会很慢,空间占用的会更大。 +但是在最坏情况下(产生大量 hash 冲突时), `unordered_map` 的各项操作的时间复杂度可达 $O(n^2)$ 。[(详情见 Codeforces 上发表的一篇卡 unordered_map 的文章)](http://codeforces.com/blog/entry/62393)而且它的遍历速度会很慢,空间占用的会更大。 diff --git a/docs/ds/stl/priority_queue.md b/docs/ds/stl/priority_queue.md index a52537f1..28e4e3ed 100644 --- a/docs/ds/stl/priority_queue.md +++ b/docs/ds/stl/priority_queue.md @@ -25,11 +25,11 @@ std::priority_queue> ## 成员函数 -1. `top()`: 访问栈顶元素 常数复杂度 -2. `empty()`: 检查底层的容器是否为空 常数复杂度 -3. `size()`: 返回底层容器的元素数量 常数复杂度 -4. `push()`: 插入元素,并对底层容器排序 最坏 $\Theta(n)$ 均摊 $\Theta(\log(n))$ -5. `pop()`: 删除第一个元素 最坏 $\Theta(\log(n))$ +1. `top()` : 访问栈顶元素 常数复杂度 +2. `empty()` : 检查底层的容器是否为空 常数复杂度 +3. `size()` : 返回底层容器的元素数量 常数复杂度 +4. `push()` : 插入元素,并对底层容器排序 最坏 $\Theta(n)$ 均摊 $\Theta(\log(n))$ +5. `pop()` : 删除第一个元素 最坏 $\Theta(\log(n))$ 由于 `std::priority_queue` 原生不支持 `modify()` / `join()` / `erase()` 故不做讲解。 diff --git a/docs/ds/stl/vector.md b/docs/ds/stl/vector.md index 376c5441..f3df3edb 100644 --- a/docs/ds/stl/vector.md +++ b/docs/ds/stl/vector.md @@ -1,22 +1,22 @@ -## 为什么要用 `vector` +## 为什么要用 `vector` -作为 OIer ,对程序效率的追求远比对工程级别的稳定性要高得多,而 vector 由于其较静态数组复杂很多的原因,时间效率在大部分情况下都要满慢于静态数组,所以在一般的正常存储数据的时候,我们是不选择 vector 的, 下面给出几个 vector 优秀的特性,在需要用到这些特性的情况下,vector 能给我们带来很大的帮助 +作为 OIer,对程序效率的追求远比对工程级别的稳定性要高得多,而 vector 由于其较静态数组复杂很多的原因,时间效率在大部分情况下都要满慢于静态数组,所以在一般的正常存储数据的时候,我们是不选择 vector 的,下面给出几个 vector 优秀的特性,在需要用到这些特性的情况下,vector 能给我们带来很大的帮助 -### `vector` 重写了比较运算符 +### `vector` 重写了比较运算符 -vector 以字典序为关键字重载了 6 个比较运算符,这使得我们可以方便的判断两个容器是否相等 (复杂度与容器大小成线性关系) +vector 以字典序为关键字重载了 6 个比较运算符,这使得我们可以方便的判断两个容器是否相等(复杂度与容器大小成线性关系) -### `vector` 的内存是动态分配的 +### `vector` 的内存是动态分配的 -由于其动态分配的特性, 所以在调用内存的常数上在很多情况下是要快于静态数组的。 +由于其动态分配的特性,所以在调用内存的常数上在很多情况下是要快于静态数组的。 -很多时候我们不能提前开好那么大的空间(eg :预处理 1~n 中所有数的约数)我们知道数据总量在空间允许的级别,但是单份数据还可能非常大,这种时候我们就需要 vector 来保证复杂度。 +很多时候我们不能提前开好那么大的空间(eg:预处理 1~n 中所有数的约数)我们知道数据总量在空间允许的级别,但是单份数据还可能非常大,这种时候我们就需要 vector 来保证复杂度。 -### `vector` 可以用赋值运算符来进行初始化 +### `vector` 可以用赋值运算符来进行初始化 由于 vector 重写了 `=` 运算符,所以我们可以方便的初始化。 -## `vector` 的构造函数 +## `vector` 的构造函数 参见如下代码 @@ -59,83 +59,83 @@ void Vector_Constructor_Test() { }; ``` -可以利用上述的方法构造一个 vector, 足够我们使用了。 +可以利用上述的方法构造一个 vector,足够我们使用了。 -## `vector` 元素访问 +## `vector` 元素访问 vector 提供了如下几种方法进行访问元素 -1. `at()` +1. `at()` - 使用方法 :`v.at(pos)` 返回 vector 中下标为 `pos` 的引用。如果数组越界抛出 `std::out_of_range` 类型的异常。 + 使用方法: `v.at(pos)` 返回 vector 中下标为 `pos` 的引用。如果数组越界抛出 `std::out_of_range` 类型的异常。 -2. `operator[]` +2. `operator[]` - 使用方法 :`v[pos]` 返回 vector 中下标为 `pos` 的引用。不执行越界检查。 + 使用方法: `v[pos]` 返回 vector 中下标为 `pos` 的引用。不执行越界检查。 -3. `front()` +3. `front()` - 使用方法 :`v.front()` 返回首元素的引用 + 使用方法: `v.front()` 返回首元素的引用 -4. `back()` +4. `back()` - 使用方法 :`v.back()` 返回末尾元素的引用 + 使用方法: `v.back()` 返回末尾元素的引用 -5. `data()` +5. `data()` - 使用方法 :`v.data()` 返回指向数组第一个元素的指针。 + 使用方法: `v.data()` 返回指向数组第一个元素的指针。 -## `vector` 迭代器 +## `vector` 迭代器 vector 提供了如下几种迭代器 -1. `begin() / cbegin()` +1. `begin()/cbegin()` - 返回指向首元素的迭代器,其中 `*begin = front` + 返回指向首元素的迭代器,其中 `*begin = front` -2. `end() / cend()` +2. `end()/cend()` 返回指向数组尾端占位符的迭代器,注意是没有元素的。 -3. `rbegin() / rcbegin()` +3. `rbegin()/rcbegin()` - 返回指向逆向数组的首元素的逆向迭代器, 可以理解为正向容器的末元素 + 返回指向逆向数组的首元素的逆向迭代器,可以理解为正向容器的末元素 -4. `rend() / rcend()` +4. `rend()/rcend()` - 返回指向逆向数组末元素后一位置的迭代器,对应容器首的前一个位置, 没有元素。 + 返回指向逆向数组末元素后一位置的迭代器,对应容器首的前一个位置,没有元素。 以上列出的迭代器中,含有字符 `c` 的为只读迭代器,你不能通过只读迭代器去修改 vector 中的元素的值。如果一个 vector 本身就是只读的,那么它的一般迭代器和只读迭代器完全等价。只读迭代器自 C++11 开始支持。 -## `vector` 容量 +## `vector` 容量 vector 有如下几种返回容量的函数 -1. `empty()` +1. `empty()` 返回一个 `bool` 值,即 `(v.begin() == v.end())` True 为空,False 为非空 -2. `size()` +2. `size()` - 返回一个元素数量,即 `(std :: distance(v.begin(), v.end()))` + 返回一个元素数量,即 `(std :: distance(v.begin(), v.end()))` -3. `shrink_to_fit()` (C++11) +3. `shrink_to_fit()` (C++11) 释放未使用的内存来减少内存使用 -此外,还有 `max_size()`, `reserve()`, `capacity()` 等 OIer 很难用到的函数,不做介绍。 +此外,还有 `max_size()` , `reserve()` , `capacity()` 等 OIer 很难用到的函数,不做介绍。 -## `vector` 修改器 +## `vector` 修改器 -- `clear()` 清除所有元素 -- `insert()` 支持在某个迭代器位置插入元素、可以插入多个**此操作是与 `pos` 距离末尾长度成线性而非常数的** -- `erase()` 删除某个迭代器或者区间的元素,返回最后被删除的迭代器。 -- `push_back()` 在末尾插入一个元素。 -- `pop_back()` 删除末尾元素。 -- `swap()` 与另一个容器进行交换,此操作是**常数复杂度**而非线性的。 +- `clear()` 清除所有元素 +- `insert()` 支持在某个迭代器位置插入元素、可以插入多个**此操作是与 `pos` 距离末尾长度成线性而非常数的** +- `erase()` 删除某个迭代器或者区间的元素,返回最后被删除的迭代器。 +- `push_back()` 在末尾插入一个元素。 +- `pop_back()` 删除末尾元素。 +- `swap()` 与另一个容器进行交换,此操作是**常数复杂度**而非线性的。 -## `vector` 特化 `std::vector` +## `vector` 特化 `std::vector` 标准库提供对 bool 的 vector 优化,其空间占用与 bitset 一样,每个 `bool` 只占 1bit,且支持动态内存 -注意,`vector`没有 bitset 的位运算重载,所以适用情况与 bitset 并不完全重合,请选择食用 +注意, `vector` 没有 bitset 的位运算重载,所以适用情况与 bitset 并不完全重合,请选择食用 diff --git a/docs/ds/treap.md b/docs/ds/treap.md index 8296af00..ea46bb4e 100644 --- a/docs/ds/treap.md +++ b/docs/ds/treap.md @@ -1,6 +1,6 @@ -treap 是一种弱平衡的二叉搜索树。treap 这个单词是 tree 和 heap 的组合,表明 treap 是一种由树和堆组合形成的数据结构。treap 的每个结点上要额外储存一个值 $priority$。treap 除了要满足二叉搜索树的性质之外,还需满足父节点的 $priority$ 大于等于两个儿子的 $priority$。而 $priority$ 是每个结点建立时随机生成的,因此 treap 是期望平衡的。 +treap 是一种弱平衡的二叉搜索树。treap 这个单词是 tree 和 heap 的组合,表明 treap 是一种由树和堆组合形成的数据结构。treap 的每个结点上要额外储存一个值 $priority$ 。treap 除了要满足二叉搜索树的性质之外,还需满足父节点的 $priority$ 大于等于两个儿子的 $priority$ 。而 $priority$ 是每个结点建立时随机生成的,因此 treap 是期望平衡的。 -treap 分为旋转式和无旋式两种。两种 treap 都易于编写,但无旋式 treap 的操作方式使得它天生支持维护序列、可持久化等特性。这里以重新实现 `set`(不可重集合)为例,介绍无旋式 treap。 +treap 分为旋转式和无旋式两种。两种 treap 都易于编写,但无旋式 treap 的操作方式使得它天生支持维护序列、可持久化等特性。这里以重新实现 `set` (不可重集合)为例,介绍无旋式 treap。 ## 无旋式 treap 的核心操作 @@ -8,7 +8,7 @@ treap 分为旋转式和无旋式两种。两种 treap 都易于编写,但无 ### 分裂(split) -分裂过程接受两个参数:根指针 $u$、关键值 $key$。结果为将根指针指向的 treap 分裂为两个 treap,第一个 treap 所有结点的关键值小于等于 $key$,第二个 treap 所有结点的关键值大于 $key$。该过程首先判断 $key$ 是否小于 $u$ 的关键值,若小于,则说明 $u$ 及其右子树全部属于第二个 treap,否则说明 $u$ 及其左子树全部属于第一个 treap。根据此判断决定应向左子树递归还是应向右子树递归,继续分裂子树。待子树分裂完成后按刚刚的判断情况连接 $u$ 的左子树或右子树到递归分裂所得的子树中。 +分裂过程接受两个参数:根指针 $u$ 、关键值 $key$ 。结果为将根指针指向的 treap 分裂为两个 treap,第一个 treap 所有结点的关键值小于等于 $key$ ,第二个 treap 所有结点的关键值大于 $key$ 。该过程首先判断 $key$ 是否小于 $u$ 的关键值,若小于,则说明 $u$ 及其右子树全部属于第二个 treap,否则说明 $u$ 及其左子树全部属于第一个 treap。根据此判断决定应向左子树递归还是应向右子树递归,继续分裂子树。待子树分裂完成后按刚刚的判断情况连接 $u$ 的左子树或右子树到递归分裂所得的子树中。 ```c++ pair split(node *u, int key) { @@ -29,7 +29,7 @@ pair split(node *u, int key) { ### 合并(merge) -合并过程接受两个参数:左 treap 的根指针 $u$、右 treap 的根指针 $v$。必须满足 $u$ 中所有结点的关键值小于等于 $v$ 中左右结点的关键值。因为两个 treap 已经有序,我们只需要考虑 $priority$ 来决定哪个 treap 应与另一个 treap 的儿子合并。若 $u$ 的根结点的 $priority$ 大于 $v$ 的,那么 $u$ 即为新根结点,$v$ 应与 $u$ 的右子树合并;反之,则 $v$ 作为新根结点,然后让 $u$ 与 $v$ 的左子树合并。不难发现,这样合并所得的树依然满足 $priority$ 的大根堆性质。 +合并过程接受两个参数:左 treap 的根指针 $u$ 、右 treap 的根指针 $v$ 。必须满足 $u$ 中所有结点的关键值小于等于 $v$ 中左右结点的关键值。因为两个 treap 已经有序,我们只需要考虑 $priority$ 来决定哪个 treap 应与另一个 treap 的儿子合并。若 $u$ 的根结点的 $priority$ 大于 $v$ 的,那么 $u$ 即为新根结点, $v$ 应与 $u$ 的右子树合并;反之,则 $v$ 作为新根结点,然后让 $u$ 与 $v$ 的左子树合并。不难发现,这样合并所得的树依然满足 $priority$ 的大根堆性质。 ```c++ node *merge(node *u, node *v) { @@ -49,7 +49,7 @@ node *merge(node *u, node *v) { } ``` -## 将 treap 包装成为 `set` +## 将 treap 包装成为 `set` ### count 函数 @@ -108,7 +108,7 @@ void erase(int key) { 因为普通的二叉搜索树会被递增或递减的数据卡,用 treap 对每个节点定义一个权值,由 rand 得到,从而防止特殊数据卡。 -每次删除 / 插入时通过 rand 值决定要不要旋转即可,其他操作与二叉搜索树类似 +每次删除/插入时通过 rand 值决定要不要旋转即可,其他操作与二叉搜索树类似 以下是 bzoj 普通平衡树模板代码 diff --git a/docs/ds/virtual-tree.md b/docs/ds/virtual-tree.md index ee396279..92c09ec9 100644 --- a/docs/ds/virtual-tree.md +++ b/docs/ds/virtual-tree.md @@ -69,8 +69,8 @@ 则: -- 若 $son[i]$ 不是关键点:$Dp[i]=Dp[i] + \min \{Dp[son[i]],w[i,son[i]]\}$; -- 若 $son[i]$ 是关键点:$Dp[i]=Dp[i] + w[i,son[i]]$。 +- 若 $son[i]$ 不是关键点: $Dp[i]=Dp[i] + \min \{Dp[son[i]],w[i,son[i]]\}$ ; +- 若 $son[i]$ 是关键点: $Dp[i]=Dp[i] + w[i,son[i]]$ 。 很好,这样我们得到了一份 $O(n\times q)$ 的代码。 @@ -86,7 +86,7 @@ 对于这题来说,我们只需要保证红色的点无法到达 $1$ 号节点就行了。 -通过肉眼观察可以得出结论——$1$ 号节点的右子树(虽然实际上可能有多个子树,但这里只有两个子树,所以暂时这么称呼了)一个红色节点都木有,**所以没必要去 DP 它**,不是吗? +通过肉眼观察可以得出结论—— $1$ 号节点的右子树(虽然实际上可能有多个子树,但这里只有两个子树,所以暂时这么称呼了)一个红色节点都木有,**所以没必要去 DP 它**,不是吗? 观察题目给出的条件,红色点(关键点)的总数是与 $n$ 同阶的,也就是说实际上一次询问中红色的点对于整棵树来说是很稀疏的,所以如果我们能让复杂度由红色点的总数来决定就好了。 @@ -119,12 +119,12 @@ 非常直观的一个方法是: - 将关键点按 DFS 序排序; -- `for` 一遍,任意两个相邻的关键点求一下 LCA,并且哈希表判重; +- `for` 一遍,任意两个相邻的关键点求一下 LCA,并且哈希表判重; - 然后根据原树中的祖先 -> 后代关系建树(然而我并不知道怎么建树)。 …… -感觉很不可做的样子。<(=┘ ̄Д ̄)┘╧═╧ +感觉很不可做的样子。<(=┘~Д~)┘╧═╧ 所以,这里我们提出一种用单调栈的做法。 @@ -142,7 +142,7 @@ ![vtree7](./images/vtree7.png) -首先我们在栈中添加节点 $1$。 +首先我们在栈中添加节点 $1$ 。 然后接下来按照 DFS 序从小到达添加关键节点。 @@ -154,7 +154,7 @@ ![vtree9](./images/vtree9.png) -那就…… 非常尴尬了 +那就……非常尴尬了 显然,当前单调栈维护的链是: @@ -166,7 +166,7 @@ 那么我们就虚树中连上这些边: -![vtree11 (复件)](./images/vtree12.png) +![vtree11(复件)](./images/vtree12.png) 并且把这两个点从栈中弹出: @@ -184,20 +184,20 @@ 那么步骤是这样的: -- 将 3 个关键点 $6,4,7$(我故意打乱了)按照 DFS 序排序,得到序列 $4,6,7$。 +- 将 3 个关键点 $6,4,7$ (我故意打乱了)按照 DFS 序排序,得到序列 $4,6,7$ 。 - 将点 $1$ 入栈。 -- 取序列第一个作为当前节点,为 $4$。再取栈顶元素,为$1$。求$1$和$4$的$LCA$:$LCA(1,4)=1$。 -- 发现$LCA(1,4)=$栈顶元素,说明它们在虚树的一条链上,所以直接把当前节点$4$入栈,当前栈为$4,1$。 -- 取序列第二个作为当前节点,为 $6$。再取栈顶元素,为$4$。求$6$和$4$的$LCA$:$LCA(6,4)=1$。 -- 发现$LCA(6,4)\neq$栈顶元素,进入判断阶段。 -- 判断阶段:发现栈顶节点$4$的 DFS 序是大于$LCA(6,4)$的,但是次大节点(栈顶节点下面的那个节点)$1$的 DFS 序是等于$LCA$的(其实 DFS 序相等说明节点也相等),说明$LCA$已经入栈了,所以直接连接$1->4$的边,也就是$LCA$到栈顶元素的边。并把$4$从栈中弹出。 -- 结束了判断阶段,将$6$入栈,当前栈为$6,1$。 -- 取序列第三个作为当前节点,为$7$。再取栈顶元素,为$6$。求$7$和$6$的$LCA$:$LCA(7,6)=3$。 -- 发现$LCA(7,6)\neq$栈顶元素,进入判断阶段。 -- 判断阶段:发现栈顶节点$6$的 DFS 序是大于$LCA(7,6)$的,但是次大节点(栈顶节点下面的那个节点)$1$的 DFS 序是小于$LCA$的,说明$LCA$还没有入过栈,所以直接连接$3->6$的边,也就是$LCA$到栈顶元素的边。把$6$从栈中弹出,并且把$LCA(6,7)$入栈。 -- 结束了判断阶段,将$7$入栈,当前栈为$1,3,7$。 +- 取序列第一个作为当前节点,为 $4$ 。再取栈顶元素,为 $1$ 。求 $1$ 和 $4$ 的 $LCA$ : $LCA(1,4)=1$ 。 +- 发现 $LCA(1,4)=$ 栈顶元素,说明它们在虚树的一条链上,所以直接把当前节点 $4$ 入栈,当前栈为 $4,1$ 。 +- 取序列第二个作为当前节点,为 $6$ 。再取栈顶元素,为 $4$ 。求 $6$ 和 $4$ 的 $LCA$ : $LCA(6,4)=1$ 。 +- 发现 $LCA(6,4)\neq$ 栈顶元素,进入判断阶段。 +- 判断阶段:发现栈顶节点 $4$ 的 DFS 序是大于 $LCA(6,4)$ 的,但是次大节点(栈顶节点下面的那个节点) $1$ 的 DFS 序是等于 $LCA$ 的(其实 DFS 序相等说明节点也相等),说明 $LCA$ 已经入栈了,所以直接连接 $1->4$ 的边,也就是 $LCA$ 到栈顶元素的边。并把 $4$ 从栈中弹出。 +- 结束了判断阶段,将 $6$ 入栈,当前栈为 $6,1$ 。 +- 取序列第三个作为当前节点,为 $7$ 。再取栈顶元素,为 $6$ 。求 $7$ 和 $6$ 的 $LCA$ : $LCA(7,6)=3$ 。 +- 发现 $LCA(7,6)\neq$ 栈顶元素,进入判断阶段。 +- 判断阶段:发现栈顶节点 $6$ 的 DFS 序是大于 $LCA(7,6)$ 的,但是次大节点(栈顶节点下面的那个节点) $1$ 的 DFS 序是小于 $LCA$ 的,说明 $LCA$ 还没有入过栈,所以直接连接 $3->6$ 的边,也就是 $LCA$ 到栈顶元素的边。把 $6$ 从栈中弹出,并且把 $LCA(6,7)$ 入栈。 +- 结束了判断阶段,将 $7$ 入栈,当前栈为 $1,3,7$ 。 - 发现序列里的 3 个节点已经全部加入过栈了,退出循环。 -- 此时栈中还有 3 个节点:$1, 3,7$,很明显它们是一条链上的,所以直接链接:$1->3$和$3->7$的边。 +- 此时栈中还有 3 个节点: $1, 3,7$ ,很明显它们是一条链上的,所以直接链接: $1->3$ 和 $3->7$ 的边。 - 虚树就建完啦! 其中有很多细节,比如我是用临接表存图的方式存虚树的,所以需要清空临接表。但是直接清空整个临接表是很慢的,所以我们在**有一个从未入栈的元素入栈的时候清空该元素对应的临接表**即可。 @@ -237,8 +237,8 @@ for (int i = 1; i < top; i += 1) 对于消耗战这题,直接在虚树上跑最开始讲的那个 DP 就行了,我们等于利用了虚树排除了那些没用的非关键节点! -- 若 $son[i]$ 不是关键点:$Dp[i]=Dp[i] + \min \{Dp[son[i]],w[i,son[i]]\}$ -- 若 $son[i]$ 是关键点:$Dp[i]=Dp[i] + w[i,son[i]]$ +- 若 $son[i]$ 不是关键点: $Dp[i]=Dp[i] + \min \{Dp[son[i]],w[i,son[i]]\}$ +- 若 $son[i]$ 是关键点: $Dp[i]=Dp[i] + w[i,son[i]]$ 于是这题很简单就过了。 diff --git a/docs/ds/wblt.md b/docs/ds/wblt.md index d1f9cea9..af6189b3 100644 --- a/docs/ds/wblt.md +++ b/docs/ds/wblt.md @@ -1,6 +1,6 @@ WBLT,全称 Weight Balanced Leafy Tree,一种不常见的平衡树写法,但是具有常数较小,可以当做可并堆使用的优点。 -类似于 WBL(weight-balanced trees,加权平衡树),WBLT 体现了 leafy 的性质, 即节点多,怎么多呢? +类似于 WBL(weight-balanced trees,加权平衡树),WBLT 体现了 leafy 的性质,即节点多,怎么多呢? 对于 n 个数,不同于 treap 等,WBLT 会建立 2n 个节点,每个节点的权值为其右儿子的权值,且右儿子的权值大于等于左儿子 @@ -12,7 +12,7 @@ WBLT,全称 Weight Balanced Leafy Tree,一种不常见的平衡树写法, 而在旋转的过程中,会产生很多垃圾节点,我们采用垃圾回收的方式就可以回收废弃节点,将建立节点的操作稍作修改即可。 -附上普通平衡树代码: +附上普通平衡树代码: ```cpp #include diff --git a/docs/geometry/2d.md b/docs/geometry/2d.md index b6b73bdc..fbc57221 100644 --- a/docs/geometry/2d.md +++ b/docs/geometry/2d.md @@ -19,13 +19,13 @@ - 向量(包括向量积) - 极坐标与极坐标系 -请先阅读 [OI Wiki - 数学 - 杂项](/math/misc/)。 +请先阅读[OI Wiki - 数学 - 杂项](/math/misc/)。 ## 图形的记录 ### 点 -在平面直角坐标系下,点用坐标表示,比如点 $(5,2)$,点 $(-1,0)$ 什么的。 +在平面直角坐标系下,点用坐标表示,比如点 $(5,2)$ ,点 $(-1,0)$ 什么的。 我们记录其横纵坐标值即可。用 `pair` 或开结构体记录均可。 @@ -41,7 +41,7 @@ #### 直线与射线 -一般在解数学题时,我们用解析式表示一条直线。有一般式 $Ax+By+C=0$,还有斜截式 $y=kx+b$,还有截距式 $\frac{x}{a}+\frac{y}{b}=1$…… 用哪种? +一般在解数学题时,我们用解析式表示一条直线。有一般式 $Ax+By+C=0$ ,还有斜截式 $y=kx+b$ ,还有截距式 $\frac{x}{a}+\frac{y}{b}=1$ ……用哪种? 这些式子最后都逃不过最后的结果——代入解方程求值。 @@ -71,17 +71,17 @@ ### 正弦定理 -在三角形 $\triangle \text{ABC}$ 中,若角 $A,B,C$ 所对边分别为 $a,b,c$,则有: +在三角形 $\triangle \text{ABC}$ 中,若角 $A,B,C$ 所对边分别为 $a,b,c$ ,则有: $$ \frac{a}{\sin A}=\frac{b}{\sin B}=\frac{c}{\sin C}=2R $$ -其中,$R$ 为 $\triangle \text{ABC}$ 的外接圆半径。 +其中, $R$ 为 $\triangle \text{ABC}$ 的外接圆半径。 ### 余弦定理 -在三角形 $\triangle \text{ABC}$ 中,若角 $A,B,C$ 所对边分别为 $a,b,c$,则有: +在三角形 $\triangle \text{ABC}$ 中,若角 $A,B,C$ 所对边分别为 $a,b,c$ ,则有: $$ \begin{aligned} @@ -99,15 +99,15 @@ $$ > 某同学:我能看出来! -我们有直线上的一点 $P$ 的直线的方向向量 $\vec v$,想知道某个点 $Q$ 在直线的哪边。 +我们有直线上的一点 $P$ 的直线的方向向量 $\vec v$ ,想知道某个点 $Q$ 在直线的哪边。 -我们利用向量积的性质,算出 $\overrightarrow {PQ}\times \vec v$。如果向量积为负,则 $Q$ 在直线上方,如果向量积为 $0$,则 $Q$ 在直线上,如果向量积为正,则 $Q$ 在直线下方。 +我们利用向量积的性质,算出 $\overrightarrow {PQ}\times \vec v$ 。如果向量积为负,则 $Q$ 在直线上方,如果向量积为 $0$ ,则 $Q$ 在直线上,如果向量积为正,则 $Q$ 在直线下方。 可以画一下图,用右手定则感受一下。 ### 快速排斥实验与跨立实验 -> 某同学:捂我嘴干什么我可以 ……%¥\*……% +> 某同学:捂我嘴干什么我可以……%¥\*……% 我们现在想判断两条线段是否相交。 @@ -135,9 +135,9 @@ $$ 未通过快速排斥实验是两线段无交点的**充分不必要条件**,我们还需要进一步判断。 -因为两线段 $a,b$ 相交,$b$ 线段的两个端点一定分布在 $a$ 线段所在直线两端;同理,$a$ 线段的两个端点一定分布在 $b$ 线段所在直线两端。我们可以直接判断一条线段的两个端点相对于另一线段所在直线的位置关系,如果不同,则两线段相交,反之则不相交。我们可以利用 3.1 中的知识帮助我们判断直线与点的位置关系。 +因为两线段 $a,b$ 相交, $b$ 线段的两个端点一定分布在 $a$ 线段所在直线两端;同理, $a$ 线段的两个端点一定分布在 $b$ 线段所在直线两端。我们可以直接判断一条线段的两个端点相对于另一线段所在直线的位置关系,如果不同,则两线段相交,反之则不相交。我们可以利用 3.1 中的知识帮助我们判断直线与点的位置关系。 -这就是**跨立实验**,如果对于两线段 $a,b$,$b$ 线段的两个端点分布在 $a$ 线段所在直线的两侧,**且** $a$ 线段的两个端点分布在 $b$ 线段所在直线的两侧,我们就说 $a,b$ 两线段**通过了跨立实验**,即两线段相交。 +这就是**跨立实验**,如果对于两线段 $a,b$ , $b$ 线段的两个端点分布在 $a$ 线段所在直线的两侧,**且** $a$ 线段的两个端点分布在 $b$ 线段所在直线的两侧,我们就说 $a,b$ 两线段**通过了跨立实验**,即两线段相交。 通过跨立实验是两线段相交的**充要条件**,因此直接利用跨立实验判断即可。 @@ -147,9 +147,9 @@ $$ > 某同学:我还能 %$#^%\*(%&)) -在计算几何中,这个问题被称为 [PIP 问题](https://en.wikipedia.org/wiki/Point_in_polygon),已经有一些成熟的解决方法,下面依次介绍。 +在计算几何中,这个问题被称为[PIP 问题](https://en.wikipedia.org/wiki/Point_in_polygon),已经有一些成熟的解决方法,下面依次介绍。 -#### 光线投射算法 _(Ray casting algorithm)_ +#### 光线投射算法_(Ray casting algorithm)_ 在[这里](https://wrf.ecse.rpi.edu//Research/Short_Notes/pnpoly.html)可以看到最原始的思路。 @@ -157,35 +157,35 @@ $$ 还有点在多边形的某一边或某顶点上,这种情况十分容易判断(留作课后作业)。 -我们考虑以该点为端点引出一条射线,如果这条射线与多边形有奇数个交点,则该点在多边形内部,否则该点在多边形外部,我们简记为**奇内偶外**。这个算法同样被称为奇偶规则 _(Even-odd rule)_。 +我们考虑以该点为端点引出一条射线,如果这条射线与多边形有奇数个交点,则该点在多边形内部,否则该点在多边形外部,我们简记为**奇内偶外**。这个算法同样被称为奇偶规则_(Even-odd rule)_。 -由于 [Jordan curve theorem](https://en.wikipedia.org/wiki/Jordan_curve_theorem),我们知道,这条射线每次与多边形的一条边相交,就切换一次与多边形的内外关系,所以统计交点数的奇偶即可。 +由于[Jordan curve theorem](https://en.wikipedia.org/wiki/Jordan_curve_theorem),我们知道,这条射线每次与多边形的一条边相交,就切换一次与多边形的内外关系,所以统计交点数的奇偶即可。 这样的射线怎么取?可以随机取这条射线所在直线的斜率,建议为无理数以避免出现射线与多边形某边重合的情况。 在原版代码中,使用的是记录多边形的数组中最后一个点作为射线上一点,这样统计时,如果出现射线过多边形某边或某顶点时,可以规定射线经过的点同在射线一侧,进而做跨立实验即可。 -#### 回转数算法 _(Winding number algorithm)_ +#### 回转数算法_(Winding number algorithm)_ -回转数是数学上的概念,是平面内闭合曲线逆时针绕过该点的总次数。很容易发现,当回转数等于 $0$ 的时候,点在曲线外部。这个算法同样被称为非零规则 _(Nonzero-rule)_。 +回转数是数学上的概念,是平面内闭合曲线逆时针绕过该点的总次数。很容易发现,当回转数等于 $0$ 的时候,点在曲线外部。这个算法同样被称为非零规则_(Nonzero-rule)_。 -如何计算呢?我们把该点与多边形的所有顶点连接起来,计算相邻两边夹角的和。注意这里的夹角是**有方向的**。如果夹角和为 $0$,则这个点在多边形外,否则在多边形内。 +如何计算呢?我们把该点与多边形的所有顶点连接起来,计算相邻两边夹角的和。注意这里的夹角是**有方向的**。如果夹角和为 $0$ ,则这个点在多边形外,否则在多边形内。 ### 求两条直线的交点 > 某同学:这还不简单联立方程 #%$&^%)(Y(\*&^UIG)) -首先,我们需要确定两条直线相交,只需判断一下两条直线的方向向量是否平行即可。如果方向向量平行,则两条直线平行,交点个数为 $0$。进一步地,若两条直线平行且过同一点,则两直线重合。 +首先,我们需要确定两条直线相交,只需判断一下两条直线的方向向量是否平行即可。如果方向向量平行,则两条直线平行,交点个数为 $0$ 。进一步地,若两条直线平行且过同一点,则两直线重合。 -那么,问题简化为我们有直线 $AB,CD$ 交于一点,想求出交点 $E$。 +那么,问题简化为我们有直线 $AB,CD$ 交于一点,想求出交点 $E$ 。 -如果两直线相交,则交点只有一个,我们记录了直线上的一个点和直线的方向向量,所以我们只需要知道这个点与交点的距离 $l$,再将这个点沿方向向量平移 $l$ 个单位长度即可。 +如果两直线相交,则交点只有一个,我们记录了直线上的一个点和直线的方向向量,所以我们只需要知道这个点与交点的距离 $l$ ,再将这个点沿方向向量平移 $l$ 个单位长度即可。 -考虑构造三角形,利用正弦定理求解 $l$,可以利用向量积构造出正弦定理。 +考虑构造三角形,利用正弦定理求解 $l$ ,可以利用向量积构造出正弦定理。 ![Intersection](./images/2d-intersection.png) -由上图可知,$|\vec a\times \vec b|=|\vec a||\vec b|\sin \beta$,$|\vec u\times \vec b|=|\vec u||\vec b|\sin \theta$。 +由上图可知, $|\vec a\times \vec b|=|\vec a||\vec b|\sin \beta$ , $|\vec u\times \vec b|=|\vec u||\vec b|\sin \theta$ 。 作商得: @@ -193,9 +193,9 @@ $$ T=\frac{|\vec u\times \vec b|}{|\vec a\times \vec b|}=\frac{|\vec u|\sin \theta}{|\vec a|\sin \beta} $$ -可以看出,$|\frac{|\vec u|\sin \theta}{\sin \beta}|=l​$。若绝对值内部式子取值为正,代表沿 $\vec a​$ 方向平移,反之则为反方向。 +可以看出, $|\frac{|\vec u|\sin \theta}{\sin \beta}|=l​$ 。若绝对值内部式子取值为正,代表沿 $\vec a​$ 方向平移,反之则为反方向。 -同时,我们将 $T$ 直接乘上 $\vec a$,就自动出现了直线的单位向量,不需要进行其他消去操作了。 +同时,我们将 $T$ 直接乘上 $\vec a$ ,就自动出现了直线的单位向量,不需要进行其他消去操作了。 于是,只需要将点 $P$ 加上 $T\vec a$ 即可得出交点。 @@ -209,7 +209,7 @@ $$ 考虑向量积的模的几何意义,我们可以利用向量积完成。 -将多边形上的点逆时针标记为 $p_1,p_2,\cdots ,p_n$,再任选一个辅助点 $O$,记向量 $\vec {v_i}=p_i-O$,那么这个多边形面积 $S$ 可以表示为: +将多边形上的点逆时针标记为 $p_1,p_2,\cdots ,p_n$ ,再任选一个辅助点 $O$ ,记向量 $\vec {v_i}=p_i-O$ ,那么这个多边形面积 $S$ 可以表示为: $$ S=\frac{1}{2}\sum_{i=1}^n |\vec {v_i}\times \overrightarrow{v_{i\bmod n+1}}| @@ -235,21 +235,20 @@ $$ ### 极角序 -!!! 例题 - [「JOI Spring Camp 2014 Day4」两个人的星座](https://www.ioi-jp.org/camp/2014/2014-sp-tasks/2014-sp-d4.pdf) - 平面内有 $n$ 个点,有三种颜色,每个点的颜色是三种中的一种。求不相交的三色三角形对数。$6\le n\le 3000$。 +!!! 例题[「JOI Spring Camp 2014 Day4」两个人的星座](https://www.ioi-jp.org/camp/2014/2014-sp-tasks/2014-sp-d4.pdf) +平面内有 $n$ 个点,有三种颜色,每个点的颜色是三种中的一种。求不相交的三色三角形对数。 $6\le n\le 3000$ 。 如果两个三角形不相交,则一定可以做出两条内公切线,如果相交或内含是做不出内公切线的。三角形的公切线可以类比圆的公切线。 -先枚举一个原点,记为 $O$,以这个点为极点,过这个点且与 $x$ 轴平行的直线作为极轴,建立极坐标系,把剩余点按极角由小到大排序。然后统计出在极轴上方和下方的每种点的个数。 +先枚举一个原点,记为 $O$ ,以这个点为极点,过这个点且与 $x$ 轴平行的直线作为极轴,建立极坐标系,把剩余点按极角由小到大排序。然后统计出在极轴上方和下方的每种点的个数。 -然后根据点枚举公切线,记枚举到的点为 $P$,初始时公切线为极轴。开始统计。那么一定存在一条公切线过点 $O$ 和点 $P$。因为公切线与三角形不相交,所以一方选择公切线上方的点,另一方一定选择下方的点。然后利用乘法原理统计方案数即可。 +然后根据点枚举公切线,记枚举到的点为 $P$ ,初始时公切线为极轴。开始统计。那么一定存在一条公切线过点 $O$ 和点 $P$ 。因为公切线与三角形不相交,所以一方选择公切线上方的点,另一方一定选择下方的点。然后利用乘法原理统计方案数即可。 统计完后转公切线,那么点 $P$ 一定改变了相对于公切线的上下位置,而其他点不动,应该只将它的位置信息改变。 -这样,可以发现,同一对三角形最终被统计了 $4$ 次,就是同一条公切线会被枚举两次,最后做出的答案应除以 $4$。 +这样,可以发现,同一对三角形最终被统计了 $4$ 次,就是同一条公切线会被枚举两次,最后做出的答案应除以 $4$ 。 -分析一下算法复杂度,我们枚举了一个原点,然后对于每一个原点将剩余点排序后线性统计。于是时间复杂度为 $O(n^2\log n)$。 +分析一下算法复杂度,我们枚举了一个原点,然后对于每一个原点将剩余点排序后线性统计。于是时间复杂度为 $O(n^2\log n)$ 。 ## 代码编写注意事项 diff --git a/docs/geometry/convex-hull.md b/docs/geometry/convex-hull.md index 4ad0ddea..a81f6de4 100644 --- a/docs/geometry/convex-hull.md +++ b/docs/geometry/convex-hull.md @@ -10,7 +10,7 @@ 其定义为: -> 对于给定集合 $X$,所有包含 $X$ 的凸集的交集 $S$ 被称为 $X$ 的**凸包**。 +> 对于给定集合 $X$ ,所有包含 $X$ 的凸集的交集 $S$ 被称为 $X$ 的**凸包**。 实际上可以理解为用一个橡皮筋包含住所有给定点的形态。 @@ -26,13 +26,13 @@ 首先把所有点以横坐标为第一关键字,纵坐标为第二关键字排序。 -显然排序后最小的元素和最大的元素一定在凸包上。而且因为是凸多边形,我们如果从一个点出发逆时针走,轨迹总是 “左拐” 的,一旦出现右拐,就说明这一段不在凸包上。因此我们可以用一个单调栈来维护上下凸壳。 +显然排序后最小的元素和最大的元素一定在凸包上。而且因为是凸多边形,我们如果从一个点出发逆时针走,轨迹总是“左拐”的,一旦出现右拐,就说明这一段不在凸包上。因此我们可以用一个单调栈来维护上下凸壳。 因为从左向右看,上下凸壳所旋转的方向不同,为了让单调栈起作用,我们首先**升序枚举**求出下凸壳,然后**降序**求出上凸壳。 -求凸壳时,一旦发现即将进栈的点($P$)和栈顶的两个点($S_1,S_2$,其中 $S_1$ 为栈顶 )行进的方向向右旋转,即叉积小于 $0$:$\overrightarrow{S_2S_1}\times \overrightarrow{S_1P}<0$,则弹出栈顶,回到上一步,继续检测,直到 $\overrightarrow{S_2S_1}\times \overrightarrow{S_1P}\ge 0$ 或者栈内仅剩一个元素为止。 +求凸壳时,一旦发现即将进栈的点( $P$ )和栈顶的两个点( $S_1,S_2$ ,其中 $S_1$ 为栈顶)行进的方向向右旋转,即叉积小于 $0$ : $\overrightarrow{S_2S_1}\times \overrightarrow{S_1P}<0$ ,则弹出栈顶,回到上一步,继续检测,直到 $\overrightarrow{S_2S_1}\times \overrightarrow{S_1P}\ge 0$ 或者栈内仅剩一个元素为止。 -通常情况下不需要保留位于凸包边上的点,因此上面一段中 $\overrightarrow{S_2S_1}\times \overrightarrow{S_1P}<0$ 这个条件中的 “$<$” 可以视情况改为 $\le$,同时后面一个条件应改为$>$。 +通常情况下不需要保留位于凸包边上的点,因此上面一段中 $\overrightarrow{S_2S_1}\times \overrightarrow{S_1P}<0$ 这个条件中的“ $<$ ”可以视情况改为 $\le$ ,同时后面一个条件应改为 $>$ 。 ##### 代码实现 @@ -74,10 +74,10 @@ $$ [UVA11626 Convex Hull](https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=78&page=show_problem&problem=2673) -[洛谷 P2742 \[USACO5.1\] 圈奶牛 Fencing the Cows](https://www.luogu.org/problemnew/show/P2742) +[洛谷 P2742\[USACO5.1\]圈奶牛 Fencing the Cows](https://www.luogu.org/problemnew/show/P2742) [POJ1873 The Fortified Forest](http://poj.org/problem?id=1873) [POJ1113 Wall](http://poj.org/problem?id=1113) -[洛谷 P3829 \[SHOI2012\] 信用卡凸包](https://www.luogu.org/problemnew/show/P3829) +[洛谷 P3829\[SHOI2012\]信用卡凸包](https://www.luogu.org/problemnew/show/P3829) diff --git a/docs/geometry/pick.md b/docs/geometry/pick.md index 8ff9e73a..07c38999 100644 --- a/docs/geometry/pick.md +++ b/docs/geometry/pick.md @@ -1,15 +1,15 @@ ## Pick 定理 -Pick 定理:给定顶点座标均是整点(或正方形格子点)的简单多边形,皮克定理说明了其面积${\displaystyle A}$和内部格点数目 ${\displaystyle i}$、边上格点数目${\displaystyle b}$的关系:${\displaystyle A=i+{\frac {b}{2}}-1}$。 +Pick 定理:给定顶点座标均是整点(或正方形格子点)的简单多边形,皮克定理说明了其面积 ${\displaystyle A}$ 和内部格点数目 ${\displaystyle i}$ 、边上格点数目 ${\displaystyle b}$ 的关系: ${\displaystyle A=i+{\frac {b}{2}}-1}$ 。 具体证明:[Pick's theorem](https://en.wikipedia.org/wiki/Pick%27s_theorem) 它有以下推广: -- 取格点的组成图形的面积为一单位。在平行四边形格点,皮克定理依然成立。套用于任意三角形格点,皮克定理则是${\displaystyle A=2 \times i+b-2}$。 -- 对于非简单的多边形${\displaystyle P}$,皮克定理 ${\displaystyle A=i+{\frac {b}{2}}-\chi (P)}$,其中${\displaystyle \chi (P)}$表示${\displaystyle P}$的**欧拉特征数**。 +- 取格点的组成图形的面积为一单位。在平行四边形格点,皮克定理依然成立。套用于任意三角形格点,皮克定理则是 ${\displaystyle A=2 \times i+b-2}$ 。 +- 对于非简单的多边形 ${\displaystyle P}$ ,皮克定理 ${\displaystyle A=i+{\frac {b}{2}}-\chi (P)}$ ,其中 ${\displaystyle \chi (P)}$ 表示 ${\displaystyle P}$ 的**欧拉特征数**。 - 高维推广:Ehrhart 多项式 -- 皮克定理和**欧拉公式**(${\displaystyle V-E+F=2}$)等价。 +- 皮克定理和**欧拉公式**( ${\displaystyle V-E+F=2}$ )等价。 ## 一道例题 (POJ 1265) @@ -21,8 +21,8 @@ Pick 定理:给定顶点座标均是整点(或正方形格子点)的简单 这道题目其实用了以下三个知识: -- 以格子点为顶点的线段,覆盖的点的个数为$\gcd(dx,dy)$,其中,$dx,dy$分别为线段横向占的点数和纵向占的点数。如果$dx$或$dy$为$0$,则覆盖的点数为$dy$**或**$dx$。 -- Pick 定理:平面上以格子点为顶点的简单多边形的面积 = 边上的点数 / 2 + 内部的点数 + 1。 +- 以格子点为顶点的线段,覆盖的点的个数为 $\gcd(dx,dy)$ ,其中, $dx,dy$ 分别为线段横向占的点数和纵向占的点数。如果 $dx$ 或 $dy$ 为 $0$ ,则覆盖的点数为 $dy$ **或** $dx$ 。 +- Pick 定理:平面上以格子点为顶点的简单多边形的面积 = 边上的点数/2 + 内部的点数 + 1。 - 任意一个多边形的面积等于按顺序求相邻两个点与原点组成的向量的叉积之和(这个也可以通过顺时针定积分求得)。 于是这题就愉快地做完了 diff --git a/docs/graph/2-sat.md b/docs/graph/2-sat.md index 4222ff20..15b26d2a 100644 --- a/docs/graph/2-sat.md +++ b/docs/graph/2-sat.md @@ -1,18 +1,18 @@ -> SAT 是适定性( Satisfiability )问题的简称 。一般形式为 k - 适定性问题,简称 k-SAT 。而当 $k>2$ 时该问题为 NP 完全的。所以我们之研究 $k=2$ 的情况。 +> SAT 是适定性(Satisfiability)问题的简称。一般形式为 k - 适定性问题,简称 k-SAT。而当 $k>2$ 时该问题为 NP 完全的。所以我们之研究 $k=2$ 的情况。 ## 定义 -2-SAT ,简单的说就是给出 $n$ 个集合,每个集合有两个元素,已知若干个 $$ ,表示 $a$ 与 $b$ 矛盾(其中 $a$ 与 $b$ 属于不同的集合)。然后从每个集合选择一个元素,判断能否一共选 $n$ 个两两不矛盾的元素。显然可能有多种选择方案,一般题中只需要求出一种即可。 +2-SAT,简单的说就是给出 $n$ 个集合,每个集合有两个元素,已知若干个 $$ ,表示 $a$ 与 $b$ 矛盾(其中 $a$ 与 $b$ 属于不同的集合)。然后从每个集合选择一个元素,判断能否一共选 $n$ 个两两不矛盾的元素。显然可能有多种选择方案,一般题中只需要求出一种即可。 ## 现实意义 比如邀请人来吃喜酒,夫妻二人必须去一个,然而某些人之间有矛盾(比如 A 先生与 B 女士有矛盾,C 女士不想和 D 先生在一起),那么我们要确定能否避免来人之间没有矛盾,有时需要方案。这是一类生活中常见的问题。 -使用布尔方程表示上述问题。设 $a$ 表示 A 先生去参加,那么 B 女士就不能参加($\neg a$);$b$ 表示 C 女士参加,那么 $\neg b$ 也一定成立(D 先生不参加)。总结一下,即 $(a \vee b)$(变量 $a, b$ 至少满足一个) 。 对这些变量关系建有向图,则有:$\neg a\Rightarrow b\wedge\neg b\Rightarrow a$($a$ 不成立则 $b$ 一定成立;同理,$b$ 不成立则 $a$ 一定成立)。建图之后,我们就可以使用缩点算法来求解 2-SAT 问题了。 +使用布尔方程表示上述问题。设 $a$ 表示 A 先生去参加,那么 B 女士就不能参加( $\neg a$ ); $b$ 表示 C 女士参加,那么 $\neg b$ 也一定成立(D 先生不参加)。总结一下,即 $(a \vee b)$ (变量 $a, b$ 至少满足一个)。对这些变量关系建有向图,则有: $\neg a\Rightarrow b\wedge\neg b\Rightarrow a$ ( $a$ 不成立则 $b$ 一定成立;同理, $b$ 不成立则 $a$ 一定成立)。建图之后,我们就可以使用缩点算法来求解 2-SAT 问题了。 ## 常用解决方法 -### Tarjan [SCC 缩点](/graph/scc) +### Tarjan[SCC 缩点](/graph/scc) 算法考究在建图这点,我们举个例子来讲: @@ -22,7 +22,7 @@ 输出方案时可以通过变量在图中的拓扑序确定该变量的取值。如果变量 $\neg x$ 的拓扑序在 $x$ 之后,那么取 $x$ 值为真。应用到 Tarjan 算法的缩点,即 $x$ 所在 SCC 编号在 $\neg x$ 之前时,取 $x$ 为真。因为 Tarjan 算法求强连通分量时使用了栈,所以 Tarjan 求得的 SCC 编号相当于反拓扑序。 -显然地, 时间复杂度为 $O(n+m)$。 +显然地,时间复杂度为 $O(n+m)$ 。 ### 爆搜 @@ -75,7 +75,7 @@ struct Twosat { ## 例题 -### **HDU3062 [Party](http://acm.hdu.edu.cn/showproblem.php?pid=3062)** +### **HDU3062[Party](http://acm.hdu.edu.cn/showproblem.php?pid=3062)** > 题面:有 n 对夫妻被邀请参加一个聚会,因为场地的问题,每对夫妻中只有 $1$ 人可以列席。在 $2n$ 个人中,某些人之间有着很大的矛盾(当然夫妻之间是没有矛盾的),有矛盾的 $2$ 个人是不会同时出现在聚会上的。有没有可能会有 $n$ 个人同时列席? @@ -163,6 +163,6 @@ int main() { ## 练习题 -HDU1814 [和平委员会](http://acm.hdu.edu.cn/showproblem.php?pid=1814) +HDU1814[和平委员会](http://acm.hdu.edu.cn/showproblem.php?pid=1814) -POJ3683 [牧师忙碌日](http://poj.org/problem?id=3683) +POJ3683[牧师忙碌日](http://poj.org/problem?id=3683) diff --git a/docs/graph/basic.md b/docs/graph/basic.md index 334da560..c90cc4a6 100644 --- a/docs/graph/basic.md +++ b/docs/graph/basic.md @@ -4,21 +4,21 @@ 什么意思呢?我们开一个数组,数组里每个元素是图的一条边。 -这样做有个缺点,每次想要知道两个点之间是否有连边(或者说一条边是否存在),都需要在数组里进行一番查找。而且如果没有对边事先排序的话,就不能使用二分查找的方法($O(\log n)$),而是每次只能按顺序找($O(n)$),成本较高。 +这样做有个缺点,每次想要知道两个点之间是否有连边(或者说一条边是否存在),都需要在数组里进行一番查找。而且如果没有对边事先排序的话,就不能使用二分查找的方法( $O(\log n)$ ),而是每次只能按顺序找( $O(n)$ ),成本较高。 -什么时候会用到这个方法呢?最简单的一个例子是使用 Kruskal 算法求 [最小生成树](/graph/mst) 的时候。 +什么时候会用到这个方法呢?最简单的一个例子是使用 Kruskal 算法求[最小生成树](/graph/mst)的时候。 ### 邻接矩阵 -邻接矩阵的英文名是 adjacency matrix。它的形式是 `bool adj[n][n]`,这里面 $n$ 是节点个数,$adj[i][j]$ 表示 $i$ 和 $j$ 之间是否有边。 +邻接矩阵的英文名是 adjacency matrix。它的形式是 `bool adj[n][n]` ,这里面 $n$ 是节点个数, $adj[i][j]$ 表示 $i$ 和 $j$ 之间是否有边。 -如果边有权值,也可以直接用 `int adj[n][n]`,直接把边权存进去。 +如果边有权值,也可以直接用 `int adj[n][n]` ,直接把边权存进去。 它的优点是可以在 $O(1)$ 时间内得到一条边是否存在,缺点是需要占用 $O(n^2)$ 的空间。对于一个稀疏的图(边相对于点数的平方比较少)来说,用邻接矩阵来存的话,成本偏高。 ### 邻接表 -邻接表英文名是 adjacency list。它的形式是 `vector adj[n]`,用 `adj[i]` 存以 $i$ 为起点的边。 +邻接表英文名是 adjacency list。它的形式是 `vector adj[n]` ,用 `adj[i]` 存以 $i$ 为起点的边。 用 `vector` 无法科学地删除,所以常用 `list` 实现。 @@ -30,13 +30,13 @@ 首先介绍一下链式前向星,本质上是用单向链表实现的邻接表。 -形式上是一个结构体:`struct edge {edge *pre, int to;} *head[N], edge[M]` +形式上是一个结构体: `struct edge {edge *pre, int to;} *head[N], edge[M]` 这个结构广泛出现于算法竞赛选手的代码中,编写简洁而且对于大多数题目效率足够高。 -其中 `head[i]` 用来存以 $i$ 为起点的边,`edge` 数组是边表。 +其中 `head[i]` 用来存以 $i$ 为起点的边, `edge` 数组是边表。 -那么什么是前向星呢?事先把 `edge` 数组排个序即可。这里可以使用 [基数排序](/basic/sort) 做到 $O(m)$。 +那么什么是前向星呢?事先把 `edge` 数组排个序即可。这里可以使用[基数排序](/basic/sort)做到 $O(m)$ 。 ## 一些跟图有关的定义 @@ -50,7 +50,7 @@ simple path,是每条边只经过了一次的路径。 ### 回路 -cycle,也称为 `环`,是起点和终点相同的路径。 +cycle,也称为 `环` ,是起点和终点相同的路径。 ### 简单回路 @@ -72,11 +72,11 @@ cycle,也称为 `环`,是起点和终点相同的路径。 ### [强连通](/graph/scc) -有向图 $G$ 强连通是指,$G$ 中任意两个节点连通。 +有向图 $G$ 强连通是指, $G$ 中任意两个节点连通。 ### [弱连通](/graph/bcc) -有向图 $G$ 弱连通是指,$G$ 中的所有边替换为无向边后,$G$ 为连通图。 +有向图 $G$ 弱连通是指, $G$ 中的所有边替换为无向边后, $G$ 为连通图。 ### 子图 @@ -106,15 +106,15 @@ cycle,也称为 `环`,是起点和终点相同的路径。 ### 稀疏图 -$m = \Theta(n)$ 的图,或者指 $m$ 相对较小的图。 + $m = \Theta(n)$ 的图,或者指 $m$ 相对较小的图。 ### 稠密图 -$m = \Theta(n^2)$ 的图,或者指 $m$ 相对较大的图。 + $m = \Theta(n^2)$ 的图,或者指 $m$ 相对较大的图。 ### 完全图 -$m = \frac{n(n-1)}{2}$ 的简单无向图。 + $m = \frac{n(n-1)}{2}$ 的简单无向图。 ### 路径的长度 diff --git a/docs/graph/bcc.md b/docs/graph/bcc.md index 005a40d8..8a844104 100644 --- a/docs/graph/bcc.md +++ b/docs/graph/bcc.md @@ -1,6 +1,6 @@ ## 简介 -在阅读下列内容之前,请务必了解 [图论基础](/graph/basic) 部分。 +在阅读下列内容之前,请务必了解[图论基础](/graph/basic)部分。 ## 定义 @@ -8,13 +8,13 @@ 在一张联通的无向图中,如果将一个点删去后,原图变成不联通的两部分,我们就说这个点是**割点**。 -在一张联通的无向图中,对于两个点 $u$ 和 $v$,如果无论删去哪条边(只能删去一条)都不能使它们不联通,我们就说 $u$ 和 $v$ **边双联通**。 +在一张联通的无向图中,对于两个点 $u$ 和 $v$ ,如果无论删去哪条边(只能删去一条)都不能使它们不联通,我们就说 $u$ 和 $v$ **边双联通**。 -在一张联通的无向图中,对于两个点 $u$ 和 $v$,如果无论删去哪个点(只能删去一个,且不能删 $u$ 和 $v$ 自己)都不能使它们不联通,我们就说 $u$ 和 $v$ **点双联通**。 +在一张联通的无向图中,对于两个点 $u$ 和 $v$ ,如果无论删去哪个点(只能删去一个,且不能删 $u$ 和 $v$ 自己)都不能使它们不联通,我们就说 $u$ 和 $v$ **点双联通**。 -边双联通具有传递性,即,若 $x,y$ 边双联通,$y,z$ 边双联通,则 $x,z$ 边双联通。 +边双联通具有传递性,即,若 $x,y$ 边双联通, $y,z$ 边双联通,则 $x,z$ 边双联通。 -点双联通**不**具有传递性,反例如下图,$A,B$ 点双联通,$B,C$ 点双联通,而 $A,C$ **不**点双联通。 +点双联通**不**具有传递性,反例如下图, $A,B$ 点双联通, $B,C$ 点双联通,而 $A,C$ **不**点双联通。 ![bcc-counterexample.png](images/bcc-counterexample.png) @@ -44,9 +44,9 @@ void DFS(int p) { 我们如何判断一条边是不是桥呢?显然,非树边和绿色的树边一定不是桥,黑色的树边一定是桥。 -如何用算法去实现以上过程呢?首先有一个比较暴力的做法,对于每一条非树边,都逐个地将它覆盖的每一条树边置成绿色,这样的时间复杂度为 $O(nm)$。 +如何用算法去实现以上过程呢?首先有一个比较暴力的做法,对于每一条非树边,都逐个地将它覆盖的每一条树边置成绿色,这样的时间复杂度为 $O(nm)$ 。 -怎么优化呢?可以用差分。对于每一条非树边,在其树上深度较小的点处打上 `-1` 标记,在其树上深度较大的点处打上 `+1` 标记。然后 $O(n)$ 求出每个点的子树内部的标记之和。对于一个点 $u$,其子树内部的标记之和等于覆盖了 $u$ 和 $u$ 的父亲之间的树边的非树边数量。若这个值非 $0$,则 $u$ 和 $u$ 的父亲之间的树边不是桥,否则是桥。 +怎么优化呢?可以用差分。对于每一条非树边,在其树上深度较小的点处打上 `-1` 标记,在其树上深度较大的点处打上 `+1` 标记。然后 $O(n)$ 求出每个点的子树内部的标记之和。对于一个点 $u$ ,其子树内部的标记之和等于覆盖了 $u$ 和 $u$ 的父亲之间的树边的非树边数量。若这个值非 $0$ ,则 $u$ 和 $u$ 的父亲之间的树边不是桥,否则是桥。 用以上的方法 $O(n+m)$ 求出每条边分别是否是桥后,两个点是边双联通的,当且仅当它们的树上路径中**不**包含桥。 @@ -60,4 +60,4 @@ void DFS(int p) { 这样,一个点不是桥,当且仅当与其相连的所有边在新图中对应的蓝点都属于同一个联通块。两个点点双联通,当且仅当它们在原图的树上路径中的所有边在新图中对应的蓝点都属于同一个联通块。 -蓝点间的连通关系可以用与求边双联通时用到的差分类似的方法维护,时间复杂度 $O(n+m)$。 +蓝点间的连通关系可以用与求边双联通时用到的差分类似的方法维护,时间复杂度 $O(n+m)$ 。 diff --git a/docs/graph/bi-graph.md b/docs/graph/bi-graph.md index fc9bd5d5..62842229 100644 --- a/docs/graph/bi-graph.md +++ b/docs/graph/bi-graph.md @@ -8,7 +8,7 @@ ![](./images/bi-graph.png) -(图源 [英文维基](https://en.wikipedia.org/wiki/Bipartite_graph)) +(图源[英文维基](https://en.wikipedia.org/wiki/Bipartite_graph)) ## 性质 @@ -25,7 +25,7 @@ 显然,直接枚举答案集合的话实在是太慢了,我们需要更高效的方法。 -考虑上文提到的性质,我们可以使用 [DFS](/search/dfs) 或者 [BFS](/search/bfs) 来遍历这张图。如果发现了奇环,那么就不是二分图,否则是。 +考虑上文提到的性质,我们可以使用[DFS](/search/dfs)或者[BFS](/search/bfs)来遍历这张图。如果发现了奇环,那么就不是二分图,否则是。 ## 应用 diff --git a/docs/graph/bridge.md b/docs/graph/bridge.md index 47153ce3..23e1692a 100644 --- a/docs/graph/bridge.md +++ b/docs/graph/bridge.md @@ -6,13 +6,13 @@ ### 如何实现? -如果我们尝试删除每个点,并且判断这个图的联通性,那么复杂度会特别的高。所以要介绍一个常用的算法:$Tarjan$。 +如果我们尝试删除每个点,并且判断这个图的联通性,那么复杂度会特别的高。所以要介绍一个常用的算法: $Tarjan$ 。 首先,我们上一个图: ![](images/bridge1.png) -很容易的看出割点是 2,而且这个图仅有这一个割点。 +很容易的看出割点是 2,而且这个图仅有这一个割点。 首先,我们按照 $DFS$ 序给他打上时间戳(访问的顺序)。 @@ -20,17 +20,17 @@ 这些信息被我们保存在一个叫做 `num` 的数组中。 -还需要另外一个数组 `low`,用它来存储不经过其父亲(你有多个那么就看你遍历到了哪个)能到达的时间戳。 +还需要另外一个数组 `low` ,用它来存储不经过其父亲(你有多个那么就看你遍历到了哪个)能到达的时间戳。 -例如 2 的话是 1, 5 和 6 是 3。 +例如 2 的话是 1,5 和 6 是 3。 -然后我们开始 $DFS$,我们判断某个点是否是割点的根据是:对于某个顶点 $u$,如果存在至少一个顶点 $v$ ( $u$ 的儿子),使得 $low_v>=num_u$,即不能回到祖先,那么 $u$ 点为割点。 +然后我们开始 $DFS$ ,我们判断某个点是否是割点的根据是:对于某个顶点 $u$ ,如果存在至少一个顶点 $v$ ( $u$ 的儿子),使得 $low_v>=num_u$ ,即不能回到祖先,那么 $u$ 点为割点。 -另外,如果搜到了自己(在环中),如果他有两个及以上的儿子,那么他一定是割点了,如果只有一个儿子,那么把它删掉,不会有任何的影响。比如下面这个图,此处形成了一个环,从树上来讲它有 2 个儿子: +另外,如果搜到了自己(在环中),如果他有两个及以上的儿子,那么他一定是割点了,如果只有一个儿子,那么把它删掉,不会有任何的影响。比如下面这个图,此处形成了一个环,从树上来讲它有 2 个儿子: -![](images/bridge3.png) +![](images/bridge3.png) -我们在访问 1 的儿子时候,假设先 $DFS$ 到了 2,然后标记用过,然后递归往下,来到了 4, 4 又来到了 3,当递归回溯的时候,会发现 3 已经被访问过了,所以不是割点。 +我们在访问 1 的儿子时候,假设先 $DFS$ 到了 2,然后标记用过,然后递归往下,来到了 4,4 又来到了 3,当递归回溯的时候,会发现 3 已经被访问过了,所以不是割点。 更新 `low` 的伪代码如下: @@ -42,7 +42,7 @@ low[u] = min(low[u], num[v]); ### 例题 -[洛谷 P3388 【模板】割点(割顶)](https://www.luogu.org/problemnew/show/P3388) +[洛谷 P3388【模板】割点(割顶)](https://www.luogu.org/problemnew/show/P3388) ### Code @@ -123,4 +123,4 @@ int main() { 割边是和是不是根节点没关系的,原来我们求割点的时候是指点 $v$ 是不可能不经过父节点 $u$ 为回到祖先节点(包括父节点),所以顶点 $u$ 是割点。如果 $low_v==num_u$ 表示还可以回到父节点,如果顶点 $v$ 不能回到祖先也没有另外一条回到父亲的路,那么 $u-v$ 这条边就是割边 -$Tarjan$ 算法还有许多用途,常用的例如求强连通分量,缩点,还有求 $2-SAT$ 的用途等。 + $Tarjan$ 算法还有许多用途,常用的例如求强连通分量,缩点,还有求 $2-SAT$ 的用途等。 diff --git a/docs/graph/dag.md b/docs/graph/dag.md index 1c6eaa6f..02a4e0cf 100644 --- a/docs/graph/dag.md +++ b/docs/graph/dag.md @@ -6,18 +6,18 @@ ## 性质 -- 能 [拓扑排序](/graph/topo) 的图,一定是有向无环图; +- 能[拓扑排序](/graph/topo)的图,一定是有向无环图; - 如果有环,那么环上的任意两个节点在任意序列中都不满足条件了。 + 如果有环,那么环上的任意两个节点在任意序列中都不满足条件了。 - 有向无环图,一定能拓扑排序; - (归纳法)假设节点数不超过 $k$ 的 有向无环图都能拓扑排序,那么对于节点数等于 $k$ 的,考虑执行拓扑排序第一步之后的情形即可。 + (归纳法)假设节点数不超过 $k$ 的 有向无环图都能拓扑排序,那么对于节点数等于 $k$ 的,考虑执行拓扑排序第一步之后的情形即可。 ## 判定 如何判定一个图是否是有向无环图呢? -检验它是否可以进行 [拓扑排序](/graph/topo) 即可。 +检验它是否可以进行[拓扑排序](/graph/topo)即可。 -当然也有另外的方法,可以对图进行一遍 [DFS](/search/dfs),在得到的 DFS 树上看看有没有连向祖先的非树边(返祖边)。如果有的话,那就有环了。 +当然也有另外的方法,可以对图进行一遍[DFS](/search/dfs),在得到的 DFS 树上看看有没有连向祖先的非树边(返祖边)。如果有的话,那就有环了。 diff --git a/docs/graph/differential-constraints.md b/docs/graph/differential-constraints.md index b29e6a74..aa60c94f 100644 --- a/docs/graph/differential-constraints.md +++ b/docs/graph/differential-constraints.md @@ -1,26 +1,26 @@ - ** 差分约束系统 ** 是一种特殊的 $n$ 元一次不等式组,它包含 $n$ 个变量 $x_1,x_2,...,x_n$ 以及 $m$ 个约束条件,每个约束条件是由两个其中的变量做差构成的,形如 $x_i-x_j\leq c_k$ ,其中 $c_k$ 是常数(可以是非负数,也可以是负数)。我们要解决的问题是:求一组解 $x_1=a_1,x_2=a_2,...,x_n=a_n$ ,使得所有的约束条件得到满足,否则判断出无解。 +**差分约束系统**是一种特殊的 $n$ 元一次不等式组,它包含 $n$ 个变量 $x_1,x_2,...,x_n$ 以及 $m$ 个约束条件,每个约束条件是由两个其中的变量做差构成的,形如 $x_i-x_j\leq c_k$ ,其中 $c_k$ 是常数(可以是非负数,也可以是负数)。我们要解决的问题是:求一组解 $x_1=a_1,x_2=a_2,...,x_n=a_n$ ,使得所有的约束条件得到满足,否则判断出无解。 -差分约束系统中的每个约束条件 $x_i-x_j\leq c_k$ 都可以变形成 $x_i\leq x_j+c_k$ ,这与单源最短路中的三角形不等式 $dist[y]\leq dist[x]+z$ 非常相似。因此,我们可以把每个变量 $x_i$ 看做图中的一个结点,对于每个约束条件 $x_i-x_j\leq c_k$ ,从结点 $j$ 向结点 $i$ 连一条长度为 $c_k$ 的有向边。 +差分约束系统中的每个约束条件 $x_i-x_j\leq c_k$ 都可以变形成 $x_i\leq x_j+c_k$ ,这与单源最短路中的三角形不等式 $dist[y]\leq dist[x]+z$ 非常相似。因此,我们可以把每个变量 $x_i$ 看做图中的一个结点,对于每个约束条件 $x_i-x_j\leq c_k$ ,从结点 $j$ 向结点 $i$ 连一条长度为 $c_k$ 的有向边。 注意到,如果 $\{a_1,a_2,...,a_n\}$ 是该差分约束系统的一组解,那么对于任意的常数 $d$ , $\{a_1+d,a_2+d,...,a_n+d\}$ 显然也是该差分约束系统的一组解,因为这样做差后 $d$ 刚好被消掉。 设 $dist[0]=0$ 并向每一个点连一条边,跑单源最短路,若图中存在负环,则给定的差分约束系统无解,否则, $x_i=dist[i]$ 为该差分约束系统的一组解。 -一般使用 Bellman-Ford 或队列优化的 Bellman-Ford(俗称 SPFA,在某些随机图跑得很快) 判断图中是否存在负环,最坏时间复杂度为 $O(nm)$ 。 +一般使用 Bellman-Ford 或队列优化的 Bellman-Ford(俗称 SPFA,在某些随机图跑得很快)判断图中是否存在负环,最坏时间复杂度为 $O(nm)$ 。 ## 常用变形技巧 -### 例题 [ luogu P1993 小 K 的农场 ](https://www.luogu.org/problemnew/show/P1993) +### 例题[luogu P1993 小 K 的农场](https://www.luogu.org/problemnew/show/P1993) -题目大意:求解差分约束系统,有 $m$ 条约束条件, 每条都为形如 $x_a-x_b\geq c_k$ , $x_a-x_b\leq c_k$ 或 $x_a=x_b$ 的形式,判断该差分约束系统有没有解。 +题目大意:求解差分约束系统,有 $m$ 条约束条件,每条都为形如 $x_a-x_b\geq c_k$ , $x_a-x_b\leq c_k$ 或 $x_a=x_b$ 的形式,判断该差分约束系统有没有解。 -| 题意 | 转化 | 连边 | -| :----------------: | :-----------------------------------------: | :---------------------------: | -| $x_a - x_b \geq c$ | $x_b - x_a \leq -c$ | `add(a, b, -c);` | -| $x_a - x_b \leq c$ | $x_a - x_b \leq c$ | `add(b, a, c);` | -| $x_a = x_b$ | $x_a - x_b \leq 0, \space x_b - x_a \leq 0$ | `add(b, a, 0), add(a, b, 0);` | +| 题意 | 转化 | 连边 | +| :------------------: | :-------------------------------------------: | :-----------------------------: | +| $x_a - x_b \geq c$ | $x_b - x_a \leq -c$ | `add(a, b, -c);` | +| $x_a - x_b \leq c$ | $x_a - x_b \leq c$ | `add(b, a, c);` | +| $x_a = x_b$ | $x_a - x_b \leq 0, \space x_b - x_a \leq 0$ | `add(b, a, 0), add(a, b, 0);` | -跑判断负环,如果不存在负环,输出 `Yes` ,否则输出 `No`。 +跑判断负环,如果不存在负环,输出 `Yes` ,否则输出 `No` 。 给出一种用 DFS-SPFA 实现的判负环(时间复杂度极度不稳定): @@ -79,11 +79,11 @@ int main() { } ``` -### 例题 [P4926 \[1007\] 倍杀测量者](https://www.luogu.org/problemnew/show/P4926) +### 例题[P4926\[1007\]倍杀测量者](https://www.luogu.org/problemnew/show/P4926) -不考虑二分等其他的东西,这里只论述差分系统 $\frac{x_i}{x_j}\leq c_k$ 的求解方法。 +不考虑二分等其他的东西,这里只论述差分系统 $\frac{x_i}{x_j}\leq c_k$ 的求解方法。 -对每个 $x_i,x_j$ 和 $c_k$ 取一个 $\log$ 就可以把乘法变成加法运算,即 $\log x_i-\log x_j \leq \log c_k$ ,这样就可以用差分约束解决了。 +对每个 $x_i,x_j$ 和 $c_k$ 取一个 $\log$ 就可以把乘法变成加法运算,即 $\log x_i-\log x_j \leq \log c_k$ ,这样就可以用差分约束解决了。 ## Bellman-Ford 判负环代码实现 @@ -108,10 +108,10 @@ bool Bellman_Ford() { ## 习题 -[ bzoj 1715: \[Usaco2006 Dec\] Wormholes 虫洞 ](https://www.lydsy.com/JudgeOnline/problem.php?id=1715) +[bzoj 1715:\[Usaco2006 Dec\]Wormholes 虫洞](https://www.lydsy.com/JudgeOnline/problem.php?id=1715) -[ bzoj 2330: \[SCOI2011\] 糖果 ](https://www.lydsy.com/JudgeOnline/problem.php?id=2330) +[bzoj 2330:\[SCOI2011\]糖果](https://www.lydsy.com/JudgeOnline/problem.php?id=2330) -[ POJ 1364 King ](http://poj.org/problem?id=1364) +[POJ 1364 King](http://poj.org/problem?id=1364) -[ POJ 2983 Is the Information Reliable? ](http://poj.org/problem?id=2983) +[POJ 2983 Is the Information Reliable?](http://poj.org/problem?id=2983) diff --git a/docs/graph/flow.md b/docs/graph/flow.md index c96390eb..c6960d34 100644 --- a/docs/graph/flow.md +++ b/docs/graph/flow.md @@ -8,28 +8,27 @@ 首先我们要认识**流量**是什么。我们知道,我们城市中的每一条路肯定有一定的宽度,而这些宽度就限定了车辆(我们也可以把红绿灯两端看为一条路的长度)。 -网络流就对**图**诠释上了这么一个值,它不同于最短路。$a$ 站向 $b$ 站有一条流量为 - $5$ 的路,那么你就只能通过 $5$ 辆(或者是其它单位)车,而且通过以后就无法再通过。我们也就可以推出几个东西: +网络流就对**图**诠释上了这么一个值,它不同于最短路。 $a$ 站向 $b$ 站有一条流量为 $5$ 的路,那么你就只能通过 $5$ 辆(或者是其它单位)车,而且通过以后就无法再通过。我们也就可以推出几个东西: -1. 我们可以先通 $n$ 辆车,再通 $m$ 辆车($n+m\le$ 这条路的流量)。 +1. 我们可以先通 $n$ 辆车,再通 $m$ 辆车( $n+m\le$ 这条路的流量)。 -2. 如果我们从 $a$ 站到 $b$ 站只剩 $20$ 的流量,那么我们有一条流量为 $15$ 的边连接 $b$ 和 $c$。我们很快能推出 $a$ **流**到 $c$ 只有 $15$ 的流量(因为后者限制了前者)。 +2. 如果我们从 $a$ 站到 $b$ 站只剩 $20$ 的流量,那么我们有一条流量为 $15$ 的边连接 $b$ 和 $c$ 。我们很快能推出 $a$ **流**到 $c$ 只有 $15$ 的流量(因为后者限制了前者)。 ### 最大流 -先认识一下 $S$ ($source$) 和 $T$ ($sink$) 的概念。$S$ 就是常说的源点,$T$ 就是汇点(也就是起点和终点,这个跟最短路的概念是一样的)。我们有一张图,要求从源点流向汇点的最大流量(可以有很多条路到达汇点),就是我们的最大流问题 ($max\ flow$),一般源点是无限流量的。 +先认识一下 $S$ ( $source$ ) 和 $T$ ( $sink$ ) 的概念。 $S$ 就是常说的源点, $T$ 就是汇点(也就是起点和终点,这个跟最短路的概念是一样的)。我们有一张图,要求从源点流向汇点的最大流量(可以有很多条路到达汇点),就是我们的最大流问题 ( $max\ flow$ ),一般源点是无限流量的。 -然后我们来认识一下**增广路**(注意路不是边),就是说,从源点到汇点,只要有 $flow$ ($flow>0$) 流过去,这条路就是增广路。在一些最大流算法中,就是将这些路**增广**(意思就是走掉这条路,带走的流量肯定就是这条路的最小流量),如图: +然后我们来认识一下**增广路**(注意路不是边),就是说,从源点到汇点,只要有 $flow$ ( $flow>0$ ) 流过去,这条路就是增广路。在一些最大流算法中,就是将这些路**增广**(意思就是走掉这条路,带走的流量肯定就是这条路的最小流量),如图: ![](./images/flow1.png) -我们从 $4$ 到 $3$,肯定可以先从流量为 $20$ 的这条边先走。那么这条边就被走掉了,不能再选,总的流量为$20$(现在)。然后我们可以这样选择: +我们从 $4$ 到 $3$ ,肯定可以先从流量为 $20$ 的这条边先走。那么这条边就被走掉了,不能再选,总的流量为 $20$ (现在)。然后我们可以这样选择: -1. $4\rightarrow2\rightarrow3$ 这条**增广路**的总流量为 $20$。到 $2$ 的时候还是 $30$,到 $3$ 了就只有 $20$ 了。 +1. $4\rightarrow2\rightarrow3$ 这条**增广路**的总流量为 $20$ 。到 $2$ 的时候还是 $30$ ,到 $3$ 了就只有 $20$ 了。 -2. $4\rightarrow2\rightarrow1\rightarrow3$ 这样子我们就很好的保留了 $30$ 的流量。 +2. $4\rightarrow2\rightarrow1\rightarrow3$ 这样子我们就很好的保留了 $30$ 的流量。 -所以我们这张图的最大流就应该是 $20+30=50$。 +所以我们这张图的最大流就应该是 $20+30=50$ 。 求最大流是很简单的,稍后我们会讲解求最大流的 $3$ 种方法。 @@ -41,11 +40,11 @@ 所有代码请看:[剪贴板](https://www.luogu.org/paste/6t8jgtxc)。 -#### Edmond-Karp 动能算法($EK$ 算法) +#### Edmond-Karp 动能算法( $EK$ 算法) 这个算法很简单,就是 DFS**找增广路**,然后对其进行**增广**。你可能会问,怎么找?怎么增广? -1. 找? 我们就从源点一直 DFS 走来走去,碰到汇点就停,然后增广(每一条路都要增广)。我们在 DFS 的时候就注意一下流量合不合法就可以了。 +1. 找?我们就从源点一直 DFS 走来走去,碰到汇点就停,然后增广(每一条路都要增广)。我们在 DFS 的时候就注意一下流量合不合法就可以了。 2. 增广?其实就是按照我们找的增广路在重新走一遍。走的时候把这条路的能够成的最大流量减一减,然后给答案加上最小流量就可以了。 @@ -53,13 +52,13 @@ ![](./images/flow2.png) -讲一下一些小细节。如果你是用邻接矩阵的话,反向边直接就是从 $table[x,y]$ 变成 $table[y,x]$。如果是常用的链式前向星,那么在加入边的时候就要先加入反向边。那么在用的时候呢,我们直接 $i\operatorname{xor}1$ 就可以了 ($i$ 为边的编号)。为什么呢? 相信大家都是知道 $\operatorname{xor}$ 的,那么我们在加入正向边后加入反向边,就是靠近的,所以可以使用 $\operatorname{xor}$。我们还要注意一开始的编号要设置为 $tot=1$,因为边要从编号 $2$ 开始,这样子 $\operatorname{xor}$ 对编号 $2,3$ 的边才有效果。 +讲一下一些小细节。如果你是用邻接矩阵的话,反向边直接就是从 $table[x,y]$ 变成 $table[y,x]$ 。如果是常用的链式前向星,那么在加入边的时候就要先加入反向边。那么在用的时候呢,我们直接 $i\operatorname{xor}1$ 就可以了 ( $i$ 为边的编号)。为什么呢?相信大家都是知道 $\operatorname{xor}$ 的,那么我们在加入正向边后加入反向边,就是靠近的,所以可以使用 $\operatorname{xor}$ 。我们还要注意一开始的编号要设置为 $tot=1$ ,因为边要从编号 $2$ 开始,这样子 $\operatorname{xor}$ 对编号 $2,3$ 的边才有效果。 ### Dinic -我们知道,一条路一条路找是十分的慢的,我们就设想可不可以很多条路一起找。答案当然是可以的。我们只需要一个问题,这些路是同时找的,如果有些路很调皮,往回 (别的路) 找,那么别的路不就是异常尴尬。 +我们知道,一条路一条路找是十分的慢的,我们就设想可不可以很多条路一起找。答案当然是可以的。我们只需要一个问题,这些路是同时找的,如果有些路很调皮,往回(别的路)找,那么别的路不就是异常尴尬。 -我们给这张图每一条边都指定一个方向,就不会出现上述情况。这时候我们就可以知道 **分层** 这个概念。我们对于每一次增广以后的图,给它进行分层。我们规定,低的级别只能去高的级别的点(而且只能高 $1$ 级别)。而级别就是它与源点的距离。我们对于每一次整体增广来一次 BFS 就可以了。 +我们给这张图每一条边都指定一个方向,就不会出现上述情况。这时候我们就可以知道**分层**这个概念。我们对于每一次增广以后的图,给它进行分层。我们规定,低的级别只能去高的级别的点(而且只能高 $1$ 级别)。而级别就是它与源点的距离。我们对于每一次整体增广来一次 BFS 就可以了。 ### ISAP @@ -77,18 +76,18 @@ 割其实就是删边的意思,当然最小割就是割掉 $X$ 条边来让 $S$ 跟 $T$ 不互通。我们要求 $X$ 条边加起来的流量综合最小。这就是最小割问题。 -其中我们要认识一个定理: **最小割**=**最大流** +其中我们要认识一个定理:**最小割**=**最大流** ### 二分图匹配 -匈牙利算法就是其中一个可撤回贪心的过程,而网络流更快,就在于 **撤回** 这一过程很快。 +匈牙利算法就是其中一个可撤回贪心的过程,而网络流更快,就在于**撤回**这一过程很快。 ### 建模 -在会了最大流和费用流后,建模显得尤为重要。就像 JZOI 的 [狼与羊的故事](https://www.luogu.org/problemnew/show/P2598),就是一个例子。 +在会了最大流和费用流后,建模显得尤为重要。就像 JZOI 的[狼与羊的故事](https://www.luogu.org/problemnew/show/P2598),就是一个例子。 **前期**遇到这种题目,暴搜?神奇 BFS?错误。我们首先要考虑一下会不会有**二分图匹配**,**最小割**的模型(一般不会有普通的最大流)。然后建立(超级)源点和(超级)汇点。什么意思?就是当很多个源点和很多个汇点的时候,我们就可以用超级源点和超级汇点代替「源点」和「汇点」的位置(也就是把超级源点连向各个源点,超级汇点连向各个汇点,方向按题意来定)。 -这是最常见的建模的方法之一,也是做二分图匹配的方法。还有很多建模方法,可以参考 [网络流建模基础](https://www.cnblogs.com/victorique/p/8560656.html)。 +这是最常见的建模的方法之一,也是做二分图匹配的方法。还有很多建模方法,可以参考[网络流建模基础](https://www.cnblogs.com/victorique/p/8560656.html)。 -来一道题练练手: [沙耶的玩偶](https://www.luogu.org/paste/z3085b8l)。 +来一道题练练手:[沙耶的玩偶](https://www.luogu.org/paste/z3085b8l)。 diff --git a/docs/graph/flow/node.md b/docs/graph/flow/node.md index 770bc559..a1f1dd51 100644 --- a/docs/graph/flow/node.md +++ b/docs/graph/flow/node.md @@ -1,4 +1,4 @@ -拆点是一种 **[网络流](/graph/flow/)** 建模思想,用来处理 **点权或者点的流量限制** 的问题。这种思路同样可以用于其他的图论算法中(比较经典的有 **分层图** ) +拆点是一种**[网络流](/graph/flow/)**建模思想,用来处理**点权或者点的流量限制**的问题。这种思路同样可以用于其他的图论算法中(比较经典的有**分层图**) ## 例题 经典问题 结点有流量限制的最大流 @@ -14,10 +14,10 @@ ![](./images/node2.png) -## 例题 [luogu P4568 \[JLOI2011\] 飞行路线](https://www.luogu.org/problemnew/show/P4568) +## 例题[luogu P4568\[JLOI2011\]飞行路线](https://www.luogu.org/problemnew/show/P4568) 题目大意:有 $n$ 个结点, $m$ 条边, $k$ 张旅行券,可以使用一张旅行券使得经过该边的边权除以二向下取整,求从结点 $s$ 到 $t$ 的最短路的长度。 当然可以使用 DP 方法解决这道题。我们考虑使用拆点的解法。 -将每个结点拆成 $k$ 个点,这样图就可以形象化地看做是 $k$ 层,每层的结点之间连上原来就有的边,边权和原来相等;若图上存在边 $$,则在当前层的 $u$ 所对应的结点和更高一层的 $v$ 所对应的结点,连接一条边权为原边权除以二向下取整的边。这样可以保证最多只使用 $k$ 次旅行券,因为每次从较低的一层到上面一层,就相当于是使用了一张旅行券。以最底层的 $s$ 所对应的点跑单元最短路即可。 +将每个结点拆成 $k$ 个点,这样图就可以形象化地看做是 $k$ 层,每层的结点之间连上原来就有的边,边权和原来相等;若图上存在边 $$ ,则在当前层的 $u$ 所对应的结点和更高一层的 $v$ 所对应的结点,连接一条边权为原边权除以二向下取整的边。这样可以保证最多只使用 $k$ 次旅行券,因为每次从较低的一层到上面一层,就相当于是使用了一张旅行券。以最底层的 $s$ 所对应的点跑单元最短路即可。 diff --git a/docs/graph/heavy-light-decomposition.md b/docs/graph/heavy-light-decomposition.md index 383f69f8..7addd42e 100644 --- a/docs/graph/heavy-light-decomposition.md +++ b/docs/graph/heavy-light-decomposition.md @@ -1,22 +1,22 @@ -在学习本部分时,请先学习 **[线段树](/ds/segment/)** 的相关内容。 +在学习本部分时,请先学习**[线段树](/ds/segment/)**的相关内容。 ## 树链剖分的思想及能解决的问题 一棵静态(形状固定的)树,要求进行几种操作: -1. 修改 **单个节点 / 树上两点之间的路径 / 一个节点的子树上** 的所有点的值。 +1. 修改**单个节点/树上两点之间的路径/一个节点的子树上**的所有点的值。 -2. 查询 **单个节点 / 树上两点之间的路径 / 一个节点的子树上** 节点的值的 **和 / 极值 / 其他(具有较强的合并性)** 。 +2. 查询**单个节点/树上两点之间的路径/一个节点的子树上**节点的值的**和/极值/其他(具有较强的合并性)**。 如果树的形态是一条链,那么我们只需要维护一个线段树,修改或查询线段树的值。 因为这是一棵树,我们将这个树剖分成多个链,并用线段树修改或查询答案,这就是树链剖分的思想。 -如果树是动态的,需要使用 **LCT** 来解决。 +如果树是动态的,需要使用**LCT**来解决。 -由于树链剖分的思想十分暴力,所以被 OIers 戏称为 **“优雅的暴力”** 。 +由于树链剖分的思想十分暴力,所以被 OIers 戏称为**“优雅的暴力”**。 -## 例题 [luogu P2590 \[ZJOI2008\] 树的统计](https://www.luogu.org/problemnew/show/P2590) +## 例题[luogu P2590\[ZJOI2008\]树的统计](https://www.luogu.org/problemnew/show/P2590) 题目大意:对一棵有 $n$ 个节点的静态树,进行三种操作共 $q$ 次: @@ -26,29 +26,29 @@ 3. 查询 $u$ 到 $v$ 的路径上的权值和。 -题目保证 $1\le n\le 30000,0\le q\le 200000$ +题目保证 $1\le n\le 30000,0\le q\le 200000$ ## 一些定义 -$fa(x)$ 表示节点 $x$ 在树上的父亲。 + $fa(x)$ 表示节点 $x$ 在树上的父亲。 -$dep(x)$ 表示节点 $x$ 在树上的深度。 + $dep(x)$ 表示节点 $x$ 在树上的深度。 -$siz(x)$ 表示节点 $x$ 的子树的节点个数。 + $siz(x)$ 表示节点 $x$ 的子树的节点个数。 -$son(x)$ 表示节点 $x$ 的 **重儿子**,即所有儿子中子树大小最大的一个。 + $son(x)$ 表示节点 $x$ 的**重儿子**,即所有儿子中子树大小最大的一个。 -定义 **重边** 表示连接两个重儿子的边。 +定义**重边**表示连接两个重儿子的边。 -定义 **重路径** 表示重边连成的一条链。 +定义**重路径**表示重边连成的一条链。 -$top(x)$ 表示节点 $x$ 所在 **重路径** 的顶部节点(深度最小)。 + $top(x)$ 表示节点 $x$ 所在**重路径**的顶部节点(深度最小)。 -$tid(x)$ 表示节点 $x$ 的 **时间戳** ,也是其在线段树中的编号。 + $tid(x)$ 表示节点 $x$ 的**时间戳**,也是其在线段树中的编号。 -$rnk(x)$ 表示时间戳所对应的节点编号,有 $rnk(tid(x))=x$。 + $rnk(x)$ 表示时间戳所对应的节点编号,有 $rnk(tid(x))=x$ 。 -我们进行两遍 DFS 预处理出这些值,其中第一次 DFS 求出 $fa(x),dep(x),siz(x),son(x)$,第二次 DFS 求出 $top(x),tid(x),rnk(x)$。 +我们进行两遍 DFS 预处理出这些值,其中第一次 DFS 求出 $fa(x),dep(x),siz(x),son(x)$ ,第二次 DFS 求出 $top(x),tid(x),rnk(x)$ 。 给出一种代码实现: @@ -77,7 +77,7 @@ void dfs2(int o, int t) { } ``` -这样构成的线段树有这样一个性质,这是原树的一个 DFS 序,一个节点的子树在线段树中是相连的, **所有重链在线段树上也是相连的** 。 +这样构成的线段树有这样一个性质,这是原树的一个 DFS 序,一个节点的子树在线段树中是相连的,**所有重链在线段树上也是相连的**。 ## 解法 @@ -93,11 +93,11 @@ void dfs2(int o, int t) { 修改一个节点的子树也很容易实现。 -问题是如何修改 / 查询两个节点之间的路径。 +问题是如何修改/查询两个节点之间的路径。 -考虑我们是如何用 **倍增法求解 LCA** 的。首先我们 **将两个节点提到同一高度,然后将两个节点一起向上跳** 。对于树链剖分也可以使用这样的思想。 +考虑我们是如何用**倍增法求解 LCA**的。首先我们**将两个节点提到同一高度,然后将两个节点一起向上跳**。对于树链剖分也可以使用这样的思想。 -在向上跳的过程中,如果当前节点在重链上,借助线段树向上跳到重链顶端,如果当前节点不在重链上,向上跳一个节点。如此直到两节点相同。沿途更新 / 查询区间信息。 +在向上跳的过程中,如果当前节点在重链上,借助线段树向上跳到重链顶端,如果当前节点不在重链上,向上跳一个节点。如此直到两节点相同。沿途更新/查询区间信息。 给出一种代码实现: @@ -271,26 +271,26 @@ int main() { ## 时间复杂度证明 -以上算法的时间复杂度为 $O(q \log^2 n)$,下证: +以上算法的时间复杂度为 $O(q \log^2 n)$ ,下证: -可以证明,如果 $u$ 是 $v$ 的父亲,且 $v$ 不是 $u$ 的重儿子,有 $siz(v)\le \frac{1}{2} siz(u)$。 +可以证明,如果 $u$ 是 $v$ 的父亲,且 $v$ 不是 $u$ 的重儿子,有 $siz(v)\le \frac{1}{2} siz(u)$ 。 -因为如果 $siz(v)> \frac{1}{2} siz(u)$,那么 $siz(v)$ 大于其他 $u$ 的儿子的 $siz$ 的和,就有 $siz(v)$ 大于其他 $u$ 的儿子的 $siz$。这样 $v$ 一定是 $u$ 的重儿子,与题设不符。 +因为如果 $siz(v)> \frac{1}{2} siz(u)$ ,那么 $siz(v)$ 大于其他 $u$ 的儿子的 $siz$ 的和,就有 $siz(v)$ 大于其他 $u$ 的儿子的 $siz$ 。这样 $v$ 一定是 $u$ 的重儿子,与题设不符。 -由此可知,每次查询操作,我们顺着 **非重链** 向上跳,其子树节点个数一定至少 **乘以 2**。如果我们顺着重链向上跳,其子树节点个数也会乘以 2,最大为 $n$。 +由此可知,每次查询操作,我们顺着**非重链**向上跳,其子树节点个数一定至少**乘以 2**。如果我们顺着重链向上跳,其子树节点个数也会乘以 2,最大为 $n$ 。 -由于一个点的子树的节点个数最多为 $n$ ,所以我们最多只需要向上跳 $O(\log n)$ 次。再乘上线段树查询的时间复杂度 $O(\log n)$ 和操作数 $O(q)$,最后的时间复杂度为 $O(q \log^2 n)$。证毕。 +由于一个点的子树的节点个数最多为 $n$ ,所以我们最多只需要向上跳 $O(\log n)$ 次。再乘上线段树查询的时间复杂度 $O(\log n)$ 和操作数 $O(q)$ ,最后的时间复杂度为 $O(q \log^2 n)$ 。证毕。 ## 练习 -[luogu P3258 \[JLOI2014\] 松鼠的新家](https://www.luogu.org/problemnew/show/P3258) (当然可以用树上差分) +[luogu P3258\[JLOI2014\]松鼠的新家](https://www.luogu.org/problemnew/show/P3258)(当然可以用树上差分) -[luogu P3178 \[HAOI2015\] 树上操作](https://www.luogu.org/problemnew/show/P3178) +[luogu P3178\[HAOI2015\]树上操作](https://www.luogu.org/problemnew/show/P3178) -[luogu P3384 【模板】树链剖分](https://www.luogu.org/problemnew/show/P3384) +[luogu P3384【模板】树链剖分](https://www.luogu.org/problemnew/show/P3384) -[luogu P2146 \[NOI2015\] 软件包管理器](https://www.luogu.org/problemnew/show/P2146) +[luogu P2146\[NOI2015\]软件包管理器](https://www.luogu.org/problemnew/show/P2146) -[luogu P2486 \[SDOI2011\] 染色](https://www.luogu.org/problemnew/show/P2486) +[luogu P2486\[SDOI2011\]染色](https://www.luogu.org/problemnew/show/P2486) -[luogu P3313 \[SDOI2014\] 旅行](https://www.luogu.org/problemnew/show/P3313) +[luogu P3313\[SDOI2014\]旅行](https://www.luogu.org/problemnew/show/P3313) diff --git a/docs/graph/index.md b/docs/graph/index.md index f489767f..4fe368fc 100644 --- a/docs/graph/index.md +++ b/docs/graph/index.md @@ -1,18 +1,18 @@ -**图论(graph theory)** 是数学的一个分支,它以 **图** 为研究的对象。 +**图论(graph theory)**是数学的一个分支,它以**图**为研究的对象。 图论本身是应用数学的一部分,历史上图论曾经被很多数学家各自独立建立过。关于图论的最早文字记载最早出现在欧拉 1736 年的论著中,也就是著名的柯尼斯堡(Konigsberg)问题(七桥问题)。 ## 图的定义 -一个图 $G$ 是一个二元组,即序偶 $\langle V,E\rangle$,或记作 $G= \langle V,E\rangle$,其中 $V$ 是有限非空集合,称为 $G$ 的顶点集, $V$ 中的元素称为顶点或结点; $E$ 称为 $G$ 的边的集合, $\forall e_i \in E$ ,都有 $V$ 中的结点与之对应,称 $e_i$ 为 $G$ 的边。 +一个图 $G$ 是一个二元组,即序偶 $\langle V,E\rangle$ ,或记作 $G= \langle V,E\rangle$ ,其中 $V$ 是有限非空集合,称为 $G$ 的顶点集, $V$ 中的元素称为顶点或结点; $E$ 称为 $G$ 的边的集合, $\forall e_i \in E$ ,都有 $V$ 中的结点与之对应,称 $e_i$ 为 $G$ 的边。 -简单来说,就是图 $G$ 就是一个结点的集合 $V$ 和边的集合 $E$,其中任意一条边都可以表示为两个结点之间的关系。若 $e_i\in E$ 表示为 $\langle u,v\rangle$ ,则有 $u\in V , v\in V$。 +简单来说,就是图 $G$ 就是一个结点的集合 $V$ 和边的集合 $E$ ,其中任意一条边都可以表示为两个结点之间的关系。若 $e_i\in E$ 表示为 $\langle u,v\rangle$ ,则有 $u\in V , v\in V$ 。 ## 有向边和无向边 -以上定义的结点对 **可以是有序的,也可以是无序的** 。如果边 $e_i$ 和结点无序对 $(u,v)$ 相对应,则称 $e_i$ 为无向边,记作 $e_i=(u,v)$,称 $u,v$ 为边 $e_i$ 的两个端点。 +以上定义的结点对**可以是有序的,也可以是无序的**。如果边 $e_i$ 和结点无序对 $(u,v)$ 相对应,则称 $e_i$ 为无向边,记作 $e_i=(u,v)$ ,称 $u,v$ 为边 $e_i$ 的两个端点。 -如果边 $e_i$ 和结点有序对 $\langle u,v\rangle$ 相对应,则称 $e_i$ 为有向边,记为 $e_i= \langle u,v\rangle$,称 $u$ 为边 $e_i$ 的 **始点** ,$v$ 为该边的终点。 +如果边 $e_i$ 和结点有序对 $\langle u,v\rangle$ 相对应,则称 $e_i$ 为有向边,记为 $e_i= \langle u,v\rangle$ ,称 $u$ 为边 $e_i$ 的**始点**, $v$ 为该边的终点。 简单来说,如果边对结点的关系是双向的,那么这条边是无向边;如果是单向的,那么这条边是有向边。 @@ -40,23 +40,23 @@ ## 结点的度数 -设图 $G= \langle V,E\rangle$ 为一个有向图, $v\in V$ ,关联于结点 $v$ 的 **边** 的条数,称为点 $v$ 的度数,记作 $deg(v)$ 。 +设图 $G= \langle V,E\rangle$ 为一个有向图, $v\in V$ ,关联于结点 $v$ 的**边**的条数,称为点 $v$ 的度数,记作 $deg(v)$ 。 注意:一个自环为它的端点增加 2 度。 -当图 $G= \langle V,E\rangle$ 为一个有向图, $v\in V$ ,称以 $v$ 作为始点的边数之和称为结点 $v$ 的出度,记为 $deg^{+} (v)$。将以 $v$ 作为终点的边数之和称为结点 $v$ 的入度,记为 $deg^{-} (v)$ 。称以 $v$ 作为端点的边数之和为结点 $v$ 的度数或度,记为 $deg(v)$ 。 +当图 $G= \langle V,E\rangle$ 为一个有向图, $v\in V$ ,称以 $v$ 作为始点的边数之和称为结点 $v$ 的出度,记为 $deg^{+} (v)$ 。将以 $v$ 作为终点的边数之和称为结点 $v$ 的入度,记为 $deg^{-} (v)$ 。称以 $v$ 作为端点的边数之和为结点 $v$ 的度数或度,记为 $deg(v)$ 。 显然, $\forall v\in V,deg(v)=deg^{+} (v)+deg^{-} (v)$ 。 ### 定理 1 -$\sum_{v\in V} deg(v)=2\times |E|$ + $\sum_{v\in V} deg(v)=2\times |E|$ 推论:在任意图中,度数为奇数的点必然有偶数个。 ### 定理 2 -$\sum_{v\in V} deg^{+} (v)=\sum_{v\in V} deg^{-} (v)=|E|$ + $\sum_{v\in V} deg^{+} (v)=\sum_{v\in V} deg^{-} (v)=|E|$ 即所有点入度之和等于出度之和。 @@ -64,19 +64,19 @@ $\sum_{v\in V} deg^{+} (v)=\sum_{v\in V} deg^{-} (v)=|E|$ 设有图 $G= \langle V,E\rangle$ 和图 $G'= \langle V',E'\rangle$ 。 -如果 $V'\subseteq V,E'\subseteq E$,则称 $G'$ 是 $G$ 的子图,记作 $G'\subseteq G$。 +如果 $V'\subseteq V,E'\subseteq E$ ,则称 $G'$ 是 $G$ 的子图,记作 $G'\subseteq G$ 。 -如果 $G'\subsetneqq G$,即 $V'\subset V$ 或 $E'\subset E$ , 则称 $G'$ 是 $G$ 的真子图,记作 $G'\subset G$。 +如果 $G'\subsetneqq G$ ,即 $V'\subset V$ 或 $E'\subset E$ ,则称 $G'$ 是 $G$ 的真子图,记作 $G'\subset G$ 。 -如果 $V'=V,E'\subseteq E$,则称 $G'$ 是 $G$ 的生成子图。 +如果 $V'=V,E'\subseteq E$ ,则称 $G'$ 是 $G$ 的生成子图。 如果 $V''\subseteq V$ 且 $V'' \neq \varnothing$ ,以 $V''$ 为结点集,以两端点均在 $V''$ 中的边为边集的 $G$ 的子图,称为 $V''$ 导出的 $G$ 的子图,简称为 $V''$ 的导出子图。 -如果 $G''= \langle V'',E''\rangle$ 使得 $E''=E-E'$ , 且 $V''$ 中仅包含 $E''$ 中的边所关联的结点,则称 $G''$ 是子图 $G'$ 相对于原图 $G$ 的补图。 +如果 $G''= \langle V'',E''\rangle$ 使得 $E''=E-E'$ ,且 $V''$ 中仅包含 $E''$ 中的边所关联的结点,则称 $G''$ 是子图 $G'$ 相对于原图 $G$ 的补图。 ## 特殊的图 -树:边数比结点数少一的连通图。更多内容,详见 [树相关基础](/graph/tree-basic/)。 +树:边数比结点数少一的连通图。更多内容,详见[树相关基础](/graph/tree-basic/)。 森林:由 $m$ 棵( $m\ge 0$ )互不相交的树组成的图。 @@ -96,4 +96,4 @@ $\sum_{v\in V} deg^{+} (v)=\sum_{v\in V} deg^{-} (v)=|E|$ ## 参考资料 -离散数学(修订版), 田文成 周禄新 编著, 天津文学出版社, P184-187 +离散数学(修订版),田文成 周禄新 编著,天津文学出版社,P184-187 diff --git a/docs/graph/lca.md b/docs/graph/lca.md index 03062f2d..4fda4d17 100644 --- a/docs/graph/lca.md +++ b/docs/graph/lca.md @@ -1,17 +1,17 @@ ## 定义 最近公共祖先简称 LCA(Lowest Common Ancestor)。两个节点的最近公共祖先,就是这两个点的公共祖先里面,离根最远的那个。 -为了方便,我们记某点集 $S={v_1,v_2,\ldots,v_n}$ 的最近公共祖先为 $\text{LCA}(v_1,v_2,\ldots,v_n)$ 或 $\text{LCA}(S)$。 +为了方便,我们记某点集 $S={v_1,v_2,\ldots,v_n}$ 的最近公共祖先为 $\text{LCA}(v_1,v_2,\ldots,v_n)$ 或 $\text{LCA}(S)$ 。 ## 性质 -1. $\text{LCA}({u})=u$; -2. $u$ 是 $v$ 的祖先,当且仅当 $\text{LCA}(u,v)=u$; +1. $\text{LCA}({u})=u$ ; +2. $u$ 是 $v$ 的祖先,当且仅当 $\text{LCA}(u,v)=u$ ; 3. 如果 $u$ 不为 $v$ 的祖先并且 $v$ 不为 $u$ 的祖先,那么 $u,v$ 分别处于 $\text{LCA}(u,v)$ 的两棵不同子树中; -4. 前序遍历中,$\text{LCA}(S)$ 出现在所有 $S$ 中元素之前,后序遍历中 $\text{LCA}(S)$ 则出现在所有 $S$ 中元素之后; -5. 两点集并的最近公共祖先为两点集分别的最近公共祖先的最近公共祖先,即 $\text{LCA}(A\cup B)=\text{LCA}(\text{LCA}(A), \text{LCA}(B))$; +4. 前序遍历中, $\text{LCA}(S)$ 出现在所有 $S$ 中元素之前,后序遍历中 $\text{LCA}(S)$ 则出现在所有 $S$ 中元素之后; +5. 两点集并的最近公共祖先为两点集分别的最近公共祖先的最近公共祖先,即 $\text{LCA}(A\cup B)=\text{LCA}(\text{LCA}(A), \text{LCA}(B))$ ; 6. 两点的最近公共祖先必定处在树上两点间的最短路上; -7. $d(u,v)=h(u)+h(v)-2h(\text{LCA}(u,v))$,其中 $d$ 是树上两点间的距离,$h$ 代表某点到树根的距离。 +7. $d(u,v)=h(u)+h(v)-2h(\text{LCA}(u,v))$ ,其中 $d$ 是树上两点间的距离, $h$ 代表某点到树根的距离。 ## 求法 @@ -22,15 +22,14 @@ ### 倍增算法 -倍增算法是最经典的 LCA 求法,他是朴素算法的改进算法。通过预处理 `fa[x][i]` 数组,游标可以快速移动,大幅减少了游标跳转次数。`fa[x][i]` 表示点 $x$ 的第 $2^i$ 个祖先。`fa[x][i]` 数组可以通过 dfs 预处理出来。 +倍增算法是最经典的 LCA 求法,他是朴素算法的改进算法。通过预处理 `fa[x][i]` 数组,游标可以快速移动,大幅减少了游标跳转次数。 `fa[x][i]` 表示点 $x$ 的第 $2^i$ 个祖先。 `fa[x][i]` 数组可以通过 dfs 预处理出来。 现在我们看看如何优化这些跳转: -在调整游标的第一阶段中,我们可以计算出 $u,v$ 两点的深度之差,设其为 $y$。通过将 $y$ 进行二进制拆分,我们将 $y$ 次游标跳转优化为 `count_one_in_binary_representation(y)` 次游标跳转。 -在第二阶段中,我们从最大的 $i$ 开始循环尝试,一直尝试到 $0$(包括 $0$),如果 `fa[u][i] != fa[v][i]`,则令 `u = fa[u][i]; v = fa[v][i]`,那么最后的 LCA 为 `fa[u][0]`。 +在调整游标的第一阶段中,我们可以计算出 $u,v$ 两点的深度之差,设其为 $y$ 。通过将 $y$ 进行二进制拆分,我们将 $y$ 次游标跳转优化为 `count_one_in_binary_representation(y)` 次游标跳转。 +在第二阶段中,我们从最大的 $i$ 开始循环尝试,一直尝试到 $0$ (包括 $0$ ),如果 `fa[u][i] != fa[v][i]` ,则令 `u = fa[u][i]; v = fa[v][i]` ,那么最后的 LCA 为 `fa[u][0]` 。 !!! 例题 - CODEVS2370 [小机房的树](http://codevs.cn/problem/2370/) - 树上最短路查询 + CODEVS2370[小机房的树](http://codevs.cn/problem/2370/)树上最短路查询 可先求出 LCA,再结合性质 $7$ 进行解答。也可以直接在求 LCA 时求出结果。 以下代码仅供参考。 @@ -106,7 +105,7 @@ int main() { ### 转化为 RMQ 问题 -首先对树进行 dfs,`dfs(root, 1)`,将深度和节点编号按顺序记录到数组中,并记录各个点在 dfs 序列中第一次出现的位置。 +首先对树进行 dfs, `dfs(root, 1)` ,将深度和节点编号按顺序记录到数组中,并记录各个点在 dfs 序列中第一次出现的位置。 ```c++ int depth[N * 2], id[N * 2], loc[N]; @@ -127,7 +126,7 @@ void dfs(int x, int dep) { 然后对 depth 数组建立支持 RMQ 查询的数据结构,需要支持查询最小值所处位置。 -当我们需要查询某点对 `(u, v)` 的 LCA 时,需要先查询区间 `[min(loc[u], loc[v]), max(loc[u], loc[v])]` 上最小值的出现位置,设其为 `pos`,则 `(u, v)` 的 LCA 为 `id[pos]`。 +当我们需要查询某点对 `(u, v)` 的 LCA 时,需要先查询区间 `[min(loc[u], loc[v]), max(loc[u], loc[v])]` 上最小值的出现位置,设其为 `pos` ,则 `(u, v)` 的 LCA 为 `id[pos]` 。 本算法不支持在线修改。 @@ -141,7 +140,7 @@ LCA 为两个游标跳转到同一条重链上时深度较小的那个游标所 ### 标准 RMQ -时间复杂度 $O(N)-O(1)$,空间复杂度 $O(N)$ ,支持在线查询,常数较大,编程复杂度较高。 +时间复杂度 $O(N)-O(1)$ ,空间复杂度 $O(N)$ ,支持在线查询,常数较大,编程复杂度较高。 流程: @@ -155,7 +154,7 @@ LCA 为两个游标跳转到同一条重链上时深度较小的那个游标所 每一步的复杂度都是 $O(N)$ 的,因此总复杂度依然是 $O(N)$ 。 -提供 RMQ 转标准 RMQ 的代码,为洛谷上 ST 表的例题[**P3865** 【模板】ST 表](https://www.luogu.org/problemnew/show/P3865) +提供 RMQ 转标准 RMQ 的代码,为洛谷上 ST 表的例题[**P3865**【模板】ST 表](https://www.luogu.org/problemnew/show/P3865) ```cpp // Copyright (C) 2018 Skqliao. All rights served. diff --git a/docs/graph/min-circle.md b/docs/graph/min-circle.md index 1e8dbbd5..d2ca58e4 100644 --- a/docs/graph/min-circle.md +++ b/docs/graph/min-circle.md @@ -4,17 +4,17 @@ ### 暴力解法 -设 $u$ 和 $v$ 之间有一条边长为 $w$ 的边,$dis(u,v)$ 表示删除 $u$ 和 $v$ 之间的连边之后,$u$ 和 $v$ 之间的最短路。 +设 $u$ 和 $v$ 之间有一条边长为 $w$ 的边, $dis(u,v)$ 表示删除 $u$ 和 $v$ 之间的连边之后, $u$ 和 $v$ 之间的最短路。 -那么最小环是 $dis(u,v)+w$。 +那么最小环是 $dis(u,v)+w$ 。 -总时间复杂度 $O(n^2m)$。 +总时间复杂度 $O(n^2m)$ 。 ### Dijkstra 枚举所有边,每一次求删除一条边之后对这条边的起点跑一次 Dijkstra,道理同上。 -时间复杂度 $O(m(n+m)\log n)$。 +时间复杂度 $O(m(n+m)\log n)$ 。 ### Floyd @@ -22,7 +22,7 @@ 怎么强迫? -对于所有的 $i$,使它自己到自己的距离为 $\infty$,也就是 +对于所有的 $i$ ,使它自己到自己的距离为 $\infty$ ,也就是 ```cpp dis[i][i] = (1 << 30); @@ -30,7 +30,7 @@ dis[i][i] = (1 << 30); 然后利用 Floyd 的性质,跑完之后对所有的 $dis[i][i]$ 取 $\min$ 即可。 -时间复杂度:$O(n^3)$ +时间复杂度: $O(n^3)$ ## 例题 @@ -42,15 +42,15 @@ GDOI2018 Day2 巡逻 2. 恢复一个被删除点以及与它有关的边 3. 询问点 $x$ 所在的最小环大小 -对于 $50\%$ 的数据,有 $n,q \le 100$ +对于 $50\%$ 的数据,有 $n,q \le 100$ 对于每一个点 $x$ 所在的简单环,都存在两条与 $x$ 相邻的边,删去其中的任意一条,简单环将变为简单路径。 那么枚举所有与 $x$ 相邻的边,每次删去其中一条,然后跑一次 Dijkstra。 -或者直接对每次询问跑一遍 Floyd 求最小环,$O(qn^3)$ +或者直接对每次询问跑一遍 Floyd 求最小环, $O(qn^3)$ -对于 $100\%$ 的数据,有 $n,q \le 400$。 +对于 $100\%$ 的数据,有 $n,q \le 400$ 。 还是利用 Floyd 求最小环的算法。 @@ -70,7 +70,7 @@ GDOI2018 Day2 巡逻 完成之后遍历一遍整棵线段树,在经过一个点时存储一个 Floyd 数组的备份,然后加入被插入在这个区间上的所有点,在离开时利用备份数组退回去即可。 -这个做法的时间复杂度为 $O(qn^2\log q)$。 +这个做法的时间复杂度为 $O(qn^2\log q)$ 。 还有一个时间复杂度更优秀的在线做法。 @@ -82,10 +82,10 @@ GDOI2018 Day2 巡逻 显然最小环包含至少两个端点在根的不同子树中一条非树边。 -假设这条边为 $(u,v)$,那么最短路树上 $x$ 到 $u$ 的路径是所有 $x$ 到 $u$ 的路径中最短的那条,$x$ 到 $v$ 的路径也是最短的那条,那么 $x\to u\to v\to x$ 这个环肯定不会比最小环要长。 +假设这条边为 $(u,v)$ ,那么最短路树上 $x$ 到 $u$ 的路径是所有 $x$ 到 $u$ 的路径中最短的那条, $x$ 到 $v$ 的路径也是最短的那条,那么 $x\to u\to v\to x$ 这个环肯定不会比最小环要长。 那么就可以枚举所有非树边,更新答案。 -每次询问的复杂度为跑一次单源最短路的复杂度,为 $O(n^2)​$。 +每次询问的复杂度为跑一次单源最短路的复杂度,为 $O(n^2)​$ 。 -总时间复杂度为 $O(qn^2)$。 +总时间复杂度为 $O(qn^2)$ 。 diff --git a/docs/graph/mst.md b/docs/graph/mst.md index 6ddc70d4..7510ab40 100644 --- a/docs/graph/mst.md +++ b/docs/graph/mst.md @@ -1,6 +1,6 @@ ## 定义 -(还记得这些定义吗?在阅读下列内容之前,请务必了解 [图论基础](/graph/basic) 部分) +(还记得这些定义吗?在阅读下列内容之前,请务必了解[图论基础](/graph/basic)部分) 生成子图 @@ -22,17 +22,17 @@ 基础:对于算法刚开始时,显然成立(最小生成树存在)。 -归纳:假设某时刻成立,当前边集为 $F$,令 $T$ 为这棵 MST,考虑下一条加入的边 $e$。 +归纳:假设某时刻成立,当前边集为 $F$ ,令 $T$ 为这棵 MST,考虑下一条加入的边 $e$ 。 -如果 $e$ 属于 $T$,那么成立。 +如果 $e$ 属于 $T$ ,那么成立。 -否则,$T+e$ 一定存在一个环,考虑这个环上不属于 $F$ 的另一条边 $f$(一定只有一条)。 +否则, $T+e$ 一定存在一个环,考虑这个环上不属于 $F$ 的另一条边 $f$ (一定只有一条)。 -首先,$f$ 的权值一定不会比 $e$ 小,不然 $f$ 会在 $e$ 之前被选取。 +首先, $f$ 的权值一定不会比 $e$ 小,不然 $f$ 会在 $e$ 之前被选取。 -然后,$f$ 的权值一定不会比 $e$ 大,不然 $T+e-f$ 就是一棵比 $T$ 还优的生成树了。 +然后, $f$ 的权值一定不会比 $e$ 大,不然 $T+e-f$ 就是一棵比 $T$ 还优的生成树了。 -所以,$T+e-f$ 包含了 $F$,并且也是一棵最小生成树,归纳成立。 +所以, $T+e-f$ 包含了 $F$ ,并且也是一棵最小生成树,归纳成立。 ### 实现 @@ -40,7 +40,7 @@ 具体来说,维护一个森林,查询两个结点是否在同一棵树中,连接两棵树。 -抽象一点地说,维护一堆 **集合**,查询两个元素是否属于同一集合,合并两个集合。 +抽象一点地说,维护一堆**集合**,查询两个元素是否属于同一集合,合并两个集合。 我们先啥都不管,假设已经实现了这个数据结构…… @@ -53,9 +53,9 @@ for (edge(u, v, len) in sorted(edges)) { } ``` -`find_set` 调用 $O(m)$ 次,merge 调用 $O(n)$ 次。 + `find_set` 调用 $O(m)$ 次,merge 调用 $O(n)$ 次。 -排序的复杂度为 $O(m \log m)$,或 $O(m)$(假设能基数排序)。 +排序的复杂度为 $O(m \log m)$ ,或 $O(m)$ (假设能基数排序)。 那么让我们模拟一下: @@ -76,13 +76,13 @@ for (edge(u, v, len) in sorted(edges)) { 我们用 $F$ 表示并查集, $E$ 表示排序后的结构体,下面是初始的状态: -$F$: + $F$ : | 编号 | 1 | 2 | 3 | 4 | | --: | --: | --: | --: | --: | | 祖宗 | 1 | 2 | 3 | 4 | -$E$: + $E$ : | 编号 | 1 | 2 | 3 | 4 | 5 | | ----: | --: | --: | --: | --: | --: | @@ -90,27 +90,27 @@ $E$: | to | 2 | 3 | 4 | 4 | 3 | | cost | 2 | 2 | 3 | 3 | 4 | -首先我们发现 1,2 是最小的,于是我们在 1 与 2 建了一条边,由于这是第一次嘛,肯定不会出现环了,并且将 1 和 2 加入一个集合: +首先我们发现 1,2 是最小的,于是我们在 1 与 2 建了一条边,由于这是第一次嘛,肯定不会出现环了,并且将 1 和 2 加入一个集合: ![](./images/mst2.png) -$F$: + $F$ : | 编号 | 1 | 2 | 3 | 4 | | --: | --: | --: | --: | --: | | 祖宗 | 1 | 1 | 3 | 4 | -接着发现 1,3,判断 3 和 1 的是不是在一个集合?发现不是,于是将 3 加进去,并且标记 3 归属1。 +接着发现 1,3,判断 3 和 1 的是不是在一个集合?发现不是,于是将 3 加进去,并且标记 3 归属 1。 ![](./images/mst3.png) -$F$: + $F$ : | 编号 | 1 | 2 | 3 | 4 | | --: | --: | --: | --: | --: | | 祖宗 | 1 | 1 | 1 | 4 | -发现 1,4,同时 1 和 4 不在一个集合,于是将 4 加进去,标记 4 也归属 1。 +发现 1,4,同时 1 和 4 不在一个集合,于是将 4 加进去,标记 4 也归属 1。 ![](./images/mst4.png) @@ -118,23 +118,23 @@ $F$: | --: | --: | --: | --: | --: | | 祖宗 | 1 | 1 | 1 | 1 | -此时,边数为点数 $-1$,整个最小生成树完成了,代价是 $2+2+3=7$。 +此时,边数为点数 $-1$ ,整个最小生成树完成了,代价是 $2+2+3=7$ 。 -### “集合” 数据结构的一种实现 +### “集合”数据结构的一种实现 只要支持两个接口:find_set 和 merge。 我们先考虑暴力,直接维护每个元素属于哪个集合,以及每个集合有哪些元素。 -find_set:$O(1)$ +find_set: $O(1)$ -merge:$O(n)$,需要将一个集合中的所有元素移到另一个集合中。 +merge: $O(n)$ ,需要将一个集合中的所有元素移到另一个集合中。 于是考虑如何优化 merge。 一个简单的思路是,将较小的集合中所有元素移到较大的集合中。 -复杂度是 $O(较小集合的大小)$。 +复杂度是 $O(较小集合的大小)$ 。 那么总时间复杂度是多少呢? @@ -144,7 +144,7 @@ merge:$O(n)$,需要将一个集合中的所有元素移到另一个集合中 所以一个元素所在的集合,最多有 $\log n$ 次,作为较小集合被合并。 -一共$n$个元素,所以总时间复杂度为 $O(n \log n + m)$。 +一共 $n$ 个元素,所以总时间复杂度为 $O(n \log n + m)$ 。 这种做法或者思想,叫「启发式合并」。 @@ -169,17 +169,17 @@ merge:$O(n)$,需要将一个集合中的所有元素移到另一个集合中 基础:只有一个结点的时候,显然成立。 -归纳:如果某一步成立,当前边集为 $F$,属于 $T$ 这棵 MST,接下来要加入边 $e$。 +归纳:如果某一步成立,当前边集为 $F$ ,属于 $T$ 这棵 MST,接下来要加入边 $e$ 。 -如果 $e$ 属于 $T$,那么成立。 +如果 $e$ 属于 $T$ ,那么成立。 -否则考虑 $T+e$ 中环上另一条可以加入当前边集的边 $f$。 +否则考虑 $T+e$ 中环上另一条可以加入当前边集的边 $f$ 。 -首先,$f$ 的权值一定不小于 $e$ 的权值,否则就会选择 $f$ 而不是 $e$ 了。 +首先, $f$ 的权值一定不小于 $e$ 的权值,否则就会选择 $f$ 而不是 $e$ 了。 -然后,$f$ 的权值一定不大于 $e$ 的权值,否则 $T+e-f$ 就是一棵更小的生成树了。 +然后, $f$ 的权值一定不大于 $e$ 的权值,否则 $T+e-f$ 就是一棵更小的生成树了。 -因此,$e$ 和 $f$ 的权值相等,$T+e-f$ 也是一棵最小生成树,且包含了 $F$。 +因此, $e$ 和 $f$ 的权值相等, $T+e-f$ 也是一棵最小生成树,且包含了 $F$ 。 ### 实现 @@ -191,11 +191,11 @@ merge:$O(n)$,需要将一个集合中的所有元素移到另一个集合中 其实跟 Dijkstra 算法一样,只要一个堆来维护距离即可。 -暴力:$O(n^2+m)$。 +暴力: $O(n^2+m)$ 。 -二叉堆:$O((n+m) \log n)$。 +二叉堆: $O((n+m) \log n)$ 。 -Fib 堆:$O(n \log n + m)$。 +Fib 堆: $O(n \log n + m)$ 。 (伪代码) @@ -233,9 +233,9 @@ Kruskal 算法中的「集合」,能否进一步优化? ## 最小生成树题目 -[\[HAOI2006\] 聪明的猴子](https://www.lydsy.com/JudgeOnline/problem.php?id=2429) +[\[HAOI2006\]聪明的猴子](https://www.lydsy.com/JudgeOnline/problem.php?id=2429) -[\[SCOI2005\] 繁忙的都市](https://www.lydsy.com/JudgeOnline/problem.php?id=1083) +[\[SCOI2005\]繁忙的都市](https://www.lydsy.com/JudgeOnline/problem.php?id=1083) ## 最小生成树的唯一性 @@ -243,7 +243,7 @@ Kruskal 算法中的「集合」,能否进一步优化? 对于 Kruskal 算法,只要计算为当前权值的边可以放几条,实际放了几条,如果这两个值不一样,那么就说明这几条边与之前的边产生了一个环(这个环中至少有两条当前权值的边,否则根据并查集,这条边是不能放的),即最小生成树不唯一。 -寻找权值与当前边相同的边,我们只需要记录头尾指针,用单调队列即可在$O(\alpha(m))$(m 为边数)的时间复杂度里优秀解决这个问题(基本与原算法时间相同)。 +寻找权值与当前边相同的边,我们只需要记录头尾指针,用单调队列即可在 $O(\alpha(m))$ (m 为边数)的时间复杂度里优秀解决这个问题(基本与原算法时间相同)。 ??? note " 例题:[POJ 1679](http://poj.org/problem?id=1679)" diff --git a/docs/graph/scc.md b/docs/graph/scc.md index 3da470ce..8b35b25d 100644 --- a/docs/graph/scc.md +++ b/docs/graph/scc.md @@ -1,6 +1,6 @@ ## 简介 -在阅读下列内容之前,请务必了解 [图论基础](/graph/basic) 部分。 +在阅读下列内容之前,请务必了解[图论基础](/graph/basic)部分。 强连通的定义是:有向图 G 强连通是指,G 中任意两个结点连通。 @@ -22,9 +22,9 @@ Tarjan 发明了很多很有用的东西,下到 NOIP 上到 CTSC 难度的都 方便起见,我们先定义一些东西。 -`dfn[x]`:结点 x 第一次被访问的时间戳 (dfs number) + `dfn[x]` :结点 x 第一次被访问的时间戳 (dfs number) -`low[x]`:结点 x 所能访问到的点的 dfn 值的最小值 + `low[x]` :结点 x 所能访问到的点的 dfn 值的最小值 这里的树指的是 DFS 树 @@ -32,7 +32,7 @@ Tarjan 发明了很多很有用的东西,下到 NOIP 上到 CTSC 难度的都 ### DFS 树的性质 -一个结点的子树内结点的 dfn 都大于该结点的 dfn。 +一个结点的子树内结点的 dfn 都大于该结点的 dfn。 从根开始的一条路径上的 dfn 严格递增。 @@ -74,9 +74,9 @@ dfs(x) { } ``` -(转自维基: ) +(转自维基:) -时间复杂度 $O(n + m)$ +时间复杂度 $O(n + m)$ ## Kosaraju 算法 @@ -86,7 +86,7 @@ Kosaraju 算法依靠两次简单的 dfs 实现。 第二次 dfs,对于反向后的图,以标号最大的顶点作为起点开始 dfs。这样遍历到的顶点集合就是一个强连通分量。对于所有未访问过的结点,选取标号最大的,重复上述过程。 -两次 dfs 结束后,强连通分量就找出来了,Kosaraju 算法的时间复杂度为 $O(n+m)$ +两次 dfs 结束后,强连通分量就找出来了,Kosaraju 算法的时间复杂度为 $O(n+m)$ ### 实现 diff --git a/docs/graph/shortest-path.md b/docs/graph/shortest-path.md index f9f58235..309e7e2c 100644 --- a/docs/graph/shortest-path.md +++ b/docs/graph/shortest-path.md @@ -1,6 +1,6 @@ ## 定义 -(还记得这些定义吗?在阅读下列内容之前,请务必了解 [图论基础](/graph/basic) 部分。) +(还记得这些定义吗?在阅读下列内容之前,请务必了解[图论基础](/graph/basic)部分。) - 路径 - 最短路 @@ -13,7 +13,7 @@ 对于边权为正的图,任意两个结点之间的最短路,不会经过重复的边。 -对于边权为正的图,任意两个结点之间的最短路,任意一条的结点数不会超过 $n$,边数不会超过 $n-1$。 +对于边权为正的图,任意两个结点之间的最短路,任意一条的结点数不会超过 $n$ ,边数不会超过 $n-1$ 。 ## Floyd 算法 @@ -25,19 +25,19 @@ ### 实现 -我们定义一个数组 `f[k][x][y]`,表示只允许经过结点 $1$ 到 $k$,结点 $x$ 到结点 $y$ 的最短路长度。 +我们定义一个数组 `f[k][x][y]` ,表示只允许经过结点 $1$ 到 $k$ ,结点 $x$ 到结点 $y$ 的最短路长度。 -很显然,`f[n][x][y]` 就是结点 $x$ 到结点 $y$ 的最短路长度。 +很显然, `f[n][x][y]` 就是结点 $x$ 到结点 $y$ 的最短路长度。 我们来考虑怎么求这个数组 -`f[0][x][y]`:边权,或者 $0$,或者 $+\infty$ (`f[0][x][x]` 什么时候应该是 $+\infty$?) + `f[0][x][y]` :边权,或者 $0$ ,或者 $+\infty$ ( `f[0][x][x]` 什么时候应该是 $+\infty$ ?) -`f[k][x][y] = min(f[k-1][x][y], f[k-1][x][k]+f[k-1][k][y])` + `f[k][x][y] = min(f[k-1][x][y], f[k-1][x][k]+f[k-1][k][y])` -上面两行都显然是对的,然而这个做法空间是 $O(N^3)$。 +上面两行都显然是对的,然而这个做法空间是 $O(N^3)$ 。 -但我们发现数组的第一维是没有用的,于是可以直接改成 `f[x][y] = min(f[x][y], f[x][k]+f[k][y])`, +但我们发现数组的第一维是没有用的,于是可以直接改成 `f[x][y] = min(f[x][y], f[x][k]+f[k][y])` , ```c++ for (k = 1; k <= n; k++) { @@ -49,7 +49,7 @@ for (k = 1; k <= n; k++) { } ``` -时间复杂度是 $O(N^3)$,空间复杂度是 $O(N^2)$。 +时间复杂度是 $O(N^3)$ ,空间复杂度是 $O(N^2)$ 。 ### 应用 @@ -93,19 +93,19 @@ for (k = 1; k <= n; k++) { 能找到某个结点出发到所有结点的最短路,或者报告某些最短路不存在。 -在国内 OI 界,你可能听说过的 “SPFA”,就是 Bellman-Ford 算法的一种实现。(优化) +在国内 OI 界,你可能听说过的“SPFA”,就是 Bellman-Ford 算法的一种实现。(优化) ### 实现 -假设结点为 $S$。 +假设结点为 $S$ 。 先定义 $dist(u)$ 为 $S$ 到 $u$ (当前)的最短路径长度。 -$relax(u,v)$: $dist(v) = min(dist(v), dist(u) + edge\_len(u, v))$. + $relax(u,v)$ : $dist(v) = min(dist(v), dist(u) + edge\_len(u, v))$ . -$relax$ 是从哪里来的呢? + $relax$ 是从哪里来的呢? -三角形不等式: $dist(v) \leq dist(u) + edge\_len(u, v)$。 +三角形不等式: $dist(v) \leq dist(u) + edge\_len(u, v)$ 。 证明:反证法,如果不满足,那么可以用 $relax$ 操作来更新 $dist(v)$ 的值。 @@ -119,19 +119,19 @@ while (1) for each edge(u, v) relax(u, v); 每次循环是 $O(m)$ 的,那么最多会循环多少次呢? -答案是 $\infty$!(如果有一个 $S$ 能走到的负环就会这样) +答案是 $\infty$ !(如果有一个 $S$ 能走到的负环就会这样) 但是此时某些结点的最短路不存在。 我们考虑最短路存在的时候。 -由于一次 $relax$ 会使(被 $relax$ 的)最短路的边数至少 $+1$,而最短路的边数最多为 $n-1$。 +由于一次 $relax$ 会使(被 $relax$ 的)最短路的边数至少 $+1$ ,而最短路的边数最多为 $n-1$ 。 -所以最多(连续)$relax$ $n-1$ 次……($relax$ 一定是环环相扣的,不然之前就能被 $relax$ 掉) +所以最多(连续) $relax$ $n-1$ 次……( $relax$ 一定是环环相扣的,不然之前就能被 $relax$ 掉) 所以最多循环 $n-1$ 次。 -总时间复杂度 $O(NM)$。 **(对于最短路存在的图)** +总时间复杂度 $O(NM)$ 。**(对于最短路存在的图)** ```text relax(u, v) { @@ -147,7 +147,7 @@ for (i = 1; i < n; i++) { } ``` -注:这里的 $edge\_len(u, v)$ 表示边的权值,如果该边不存在则为 $+\infty$,$u=v$ 则为 $0$。 +注:这里的 $edge\_len(u, v)$ 表示边的权值,如果该边不存在则为 $+\infty$ , $u=v$ 则为 $0$ 。 ### 应用 @@ -163,9 +163,9 @@ for (i = 1; i < n; i++) { 很多时候我们并不需要那么多无用的 $relax$ 操作。 -很显然,只有上一次被 $relax$ 的结点,所连接的边,才有可能引起下一次的 $relax$。 +很显然,只有上一次被 $relax$ 的结点,所连接的边,才有可能引起下一次的 $relax$ 。 -那么我们用队列来维护 “哪些结点可能会引起 $relax$”,就能只访问必要的边了。 +那么我们用队列来维护“哪些结点可能会引起 $relax$ ”,就能只访问必要的边了。 ```text q = new queue(); @@ -183,7 +183,7 @@ while (!q.empty()) { } ``` -SPFA 的时间复杂度为 $O(kM)~ (k\approx 2)$ (玄学),但 **理论上界** 为 $O(NM)$,精心设计的稠密图可以随便卡掉 SPFA,所以考试时谨慎使用 (NOI 2018 卡 SPFA)。 +SPFA 的时间复杂度为 $O(kM)~ (k\approx 2)$ (玄学),但**理论上界**为 $O(NM)$ ,精心设计的稠密图可以随便卡掉 SPFA,所以考试时谨慎使用(NOI 2018 卡 SPFA)。 #### SPFA 的优化之 SLF @@ -199,7 +199,7 @@ SPFA 的时间复杂度为 $O(kM)~ (k\approx 2)$ (玄学),但 **理论上 Dijkstra 是个人名(荷兰姓氏)。 -IPA: /ˈdikstrɑ/ 或 /ˈdɛikstrɑ/。 +IPA:/ˈdikstrɑ/或/ˈdɛikstrɑ/。 这种算法只适用于非负权图,但是时间复杂度非常优秀。 @@ -209,25 +209,25 @@ IPA: /ˈdikstrɑ/ 或 /ˈdɛikstrɑ/。 主要思想是,将结点分成两个集合:已确定最短路长度的,未确定的。 -一开始第一个集合里只有 $S$。 +一开始第一个集合里只有 $S$ 。 然后重复这些操作: -(1)$relax$ 那些刚刚被加入第一个集合的结点的所有出边。 +(1) $relax$ 那些刚刚被加入第一个集合的结点的所有出边。 (2)从第二个集合中,选取一个最短路长度最小的结点,移到第一个集合中。 直到第二个集合为空,算法结束。 -时间复杂度:只用分析集合操作,$n$ 次 `delete-min`,$m$ 次 `decrease-key`。 +时间复杂度:只用分析集合操作, $n$ 次 `delete-min` , $m$ 次 `decrease-key` 。 -如果用暴力: $O(n^2 + m)$。 +如果用暴力: $O(n^2 + m)$ 。 -如果用堆:$O((n+m) \log m)$。 +如果用堆: $O((n+m) \log m)$ 。 -如果用线段树(ZKW 线段树):$(O(n+m)\log n)$ +如果用线段树(ZKW 线段树): $(O(n+m)\log n)$ -如果用 Fibonacci 堆: $O(n \log n + m)$(这就是为啥优秀了)。 +如果用 Fibonacci 堆: $O(n \log n + m)$ (这就是为啥优秀了)。 等等,还没说正确性呢! @@ -235,7 +235,7 @@ IPA: /ˈdikstrɑ/ 或 /ˈdɛikstrɑ/。 再证明第一个集合中的元素的最短路已经确定。 -第一步,一开始时成立(基础),在每一步中,加入集合的元素一定是最大值,且是另一边最小值,$relax$ 又是加上非负数,所以仍然成立。(归纳) (利用非负权值的性质) +第一步,一开始时成立(基础),在每一步中,加入集合的元素一定是最大值,且是另一边最小值, $relax$ 又是加上非负数,所以仍然成立。(归纳)(利用非负权值的性质) 第二步,考虑每次加进来的结点,到他的最短路,上一步必然是第一个集合中的元素(否则他不会是第二个集合中的最小值,而且有第一步的性质),又因为第一个集合已经全部 $relax$ 过了,所以最短路显然确定了。 @@ -257,29 +257,29 @@ for (i = 1; i <= n; i++) { ## 不同方法的比较 -| Floyd | Bellman-Ford | Dijkstra | -| ---------- | ------------ | ---------------- | -| 每对结点之间的最短路 | 单源最短路 | 单源最短路 | -| 没有负环的图 | 任意图 | 非负权图 | -| $O(N^3)$ | $O(NM)$ | $O((N+M)\log M)$ | +| Floyd | Bellman-Ford | Dijkstra | +| ---------- | ------------ | ------------------ | +| 每对结点之间的最短路 | 单源最短路 | 单源最短路 | +| 没有负环的图 | 任意图 | 非负权图 | +| $O(N^3)$ | $O(NM)$ | $O((N+M)\log M)$ | ## 输出方案 开一个 `pre` 数组,在更新距离的时候记录下来后面的点是如何转移过去的,算法结束前再递归地输出路径即可。 -比如 Floyd 就要记录 `pre[i][j] = k;`,Bellman-Ford 和 Dijkstra 一般记录 `pre[v] = u`。 +比如 Floyd 就要记录 `pre[i][j] = k;` ,Bellman-Ford 和 Dijkstra 一般记录 `pre[v] = u` 。 ## 拓展:分层图最短路 -分层图最短路,一般模型为有 $k$ 次零代价通过一条路径,求总的最小花费。对于这种题目,我们可以采用 DP 相关的思想,设 $\text{dis}_{i, j}$ 表示当前从起点 $i$ 号结点,使用了 $j$ 次免费通行权限后的最短路径。显然,$\text{dis}$ 数组可以这么转移: +分层图最短路,一般模型为有 $k$ 次零代价通过一条路径,求总的最小花费。对于这种题目,我们可以采用 DP 相关的思想,设 $\text{dis}_{i, j}$ 表示当前从起点 $i$ 号结点,使用了 $j$ 次免费通行权限后的最短路径。显然, $\text{dis}$ 数组可以这么转移: -$\text{dis}_{i, j} = \min\{\min\{\text{dis}_{from, j - 1}\}, \min\{\text{dis}_{from,j} + w\}\}$ + $\text{dis}_{i, j} = \min\{\min\{\text{dis}_{from, j - 1}\}, \min\{\text{dis}_{from,j} + w\}\}$ -其中,$from$ 表示 $i$ 的父亲节点,$w$ 表示当前所走的边的边权。当 $j - 1 \geq k$ 时,$\text{dis}_{from, j}$ = $\infty$。 +其中, $from$ 表示 $i$ 的父亲节点, $w$ 表示当前所走的边的边权。当 $j - 1 \geq k$ 时, $\text{dis}_{from, j}$ = $\infty$ 。 事实上,这个 DP 就相当于把每个结点拆分成了 $k+1$ 个结点,每个新结点代表使用不同多次免费通行后到达的原图结点。换句话说,就是每个结点 $u_i$ 表示使用 $i$ 次免费通行权限后到达 $u$ 结点。 -### 模板题:[\[JLOI2011\] 飞行路线](https://www.luogu.org/problemnew/show/P4568) +### 模板题:[\[JLOI2011\]飞行路线](https://www.luogu.org/problemnew/show/P4568) 题意:有一个 $n$ 个点 $m$ 条边的无向图,你可以选择 $k$ 条道路以零代价通行,求 $s$ 到 $t$ 的最小花费。 diff --git a/docs/graph/topo.md b/docs/graph/topo.md index 3ead5896..2f67c0d8 100644 --- a/docs/graph/topo.md +++ b/docs/graph/topo.md @@ -4,13 +4,13 @@ 拓扑排序要解决的问题是给一个图的所有节点排序。 -我们可以拿大学选课的例子来描述这个过程, 比如学习大学课程中有: 单变量微积分, 线性代数, 离散数学概述, 概率论与统计学概述, 语言基础, 算法导论, 机器学习。 当我们想要学习 算法导论 的时候, 就必须先学会 离散数学概述 和 概率论与统计学概述, 不然在课堂就会听的一脸懵逼。 当然还有一个更加前的课程 单变量微积分。 这些课程就相当于几个顶点 $u$, 顶点之间的有向边 $(u,v)$ 就相当于学习课程的顺序。显然拓扑排序不是那么的麻烦, 不然你是如何选出合适的学习顺序。下面将介绍如何将这个过程抽象出来, 用算法来实现。 +我们可以拿大学选课的例子来描述这个过程,比如学习大学课程中有:单变量微积分,线性代数,离散数学概述,概率论与统计学概述,语言基础,算法导论,机器学习。当我们想要学习 算法导论 的时候,就必须先学会 离散数学概述 和 概率论与统计学概述,不然在课堂就会听的一脸懵逼。当然还有一个更加前的课程 单变量微积分。这些课程就相当于几个顶点 $u$ , 顶点之间的有向边 $(u,v)$ 就相当于学习课程的顺序。显然拓扑排序不是那么的麻烦,不然你是如何选出合适的学习顺序。下面将介绍如何将这个过程抽象出来,用算法来实现。 -但是如果某一天排课的老师打瞌睡了, 说想要学习 算法导论, 还得先学 机器学习, 而 机器学习 的前置课程又是 算法导论, 然后你就一万脸懵逼了, 我到底应该先学哪一个 ? 当然我们在这里不考虑什么同时学几个课程的情况。在这里, 算法导论 和 机器学习 间就出现了一个环, 显然你现在没办法弄清楚你需要学什么了, 于是你也没办法进行拓扑排序了。因而如果有向图中存在环路, 那么我们就没办法进行 拓扑排序 了。 +但是如果某一天排课的老师打瞌睡了,说想要学习 算法导论,还得先学 机器学习,而 机器学习 的前置课程又是 算法导论,然后你就一万脸懵逼了,我到底应该先学哪一个 ? 当然我们在这里不考虑什么同时学几个课程的情况。在这里,算法导论 和 机器学习 间就出现了一个环,显然你现在没办法弄清楚你需要学什么了,于是你也没办法进行拓扑排序了。因而如果有向图中存在环路,那么我们就没办法进行 拓扑排序 了。 -因此我们可以说 在一个 [DAG (有向无环图)](/graph/dag) 中, 我们间图中的顶点以线性方式进行排序, 使得对于任何的顶点 $u$ 到 $v$ 的有向边 $(u,v)$ , 都可以有 $u$ 在 $v$ 的前面。 +因此我们可以说 在一个[DAG(有向无环图)](/graph/dag)中,我们间图中的顶点以线性方式进行排序,使得对于任何的顶点 $u$ 到 $v$ 的有向边 $(u,v)$ , 都可以有 $u$ 在 $v$ 的前面。 -还有给定一个 [DAG (有向无环图)](/graph/dag),如果从 $i$ 到 $j$ 有边,则认为 $j$ 依赖于 $i$。如果 $i$ 到 $j$ 有路径($i$ 可达 $j$),则称 $j$ 间接依赖于 $i$。 +还有给定一个[DAG(有向无环图)](/graph/dag),如果从 $i$ 到 $j$ 有边,则认为 $j$ 依赖于 $i$ 。如果 $i$ 到 $j$ 有路径( $i$ 可达 $j$ ),则称 $j$ 间接依赖于 $i$ 。 拓扑排序的目标是将所有节点排序,使得排在前面的节点不能依赖于排在后面的节点。 @@ -18,11 +18,11 @@ 将入度为 0 的边组成一个集合 $S$ -每次从 $S$ 里面取出一个顶点 $v$ (可以随便取) 放入 $L$, 然后遍历顶点 $v$ 的所有边$(u_1, v), (u_2, v), (u_3, v) \cdots$, 并删除, 并判断如果该边的另一个顶点, 如果在移除这一条边后入度为 0 , 那么就将这个顶点放入集合 $L$ 中。不断地重复取出顶点然后…… +每次从 $S$ 里面取出一个顶点 $v$ (可以随便取)放入 $L$ , 然后遍历顶点 $v$ 的所有边 $(u_1, v), (u_2, v), (u_3, v) \cdots$ , 并删除,并判断如果该边的另一个顶点,如果在移除这一条边后入度为 0 , 那么就将这个顶点放入集合 $L$ 中。不断地重复取出顶点然后…… -最后当集合为空后, 就检查图中是否存在任何边。如果有, 那么这个图一定有环路, 否者返回 $L$ , $L$ 中顺序就是拓扑排序的结果 +最后当集合为空后,就检查图中是否存在任何边。如果有,那么这个图一定有环路,否者返回 $L$ , $L$ 中顺序就是拓扑排序的结果 -首先看来自 [Wiki](https://en.wikipedia.org/wiki/Topological_sorting#Kahn's_algorithm) 的伪代码 +首先看来自[Wiki](https://en.wikipedia.org/wiki/Topological_sorting#Kahn's_algorithm)的伪代码 ```text L← Empty list that will contain the sorted elements @@ -40,19 +40,19 @@ else return L (a topologically sortedorder) ``` -代码的核心是, 是维持一个入度为 0 的顶点。 +代码的核心是,是维持一个入度为 0 的顶点。 可以参考该图 ![1341373589_4609](images/1341373589_4609.png) -对其排序的结果就是: 2 -> 8 -> 0 -> 3 -> 7 -> 1 -> 5 -> 6 -> 9 -> 4 -> 11 -> 10 -> 12 +对其排序的结果就是:2 -> 8 -> 0 -> 3 -> 7 -> 1 -> 5 -> 6 -> 9 -> 4 -> 11 -> 10 -> 12 ### 时间复杂度 -假设这个图 $G = (V, E)$在初始化入度为 0 的集合 $S$ 的时候就需要遍历整个图, 并检查每一条边, 因而有 $\mathcal{O}(E+V)$ 的复杂度. 然后对该集合进行操作, 显然也是需要 $\mathcal{O}(E+V)$ 的时间复杂度。 +假设这个图 $G = (V, E)$ 在初始化入度为 0 的集合 $S$ 的时候就需要遍历整个图,并检查每一条边,因而有 $\mathcal{O}(E+V)$ 的复杂度。然后对该集合进行操作,显然也是需要 $\mathcal{O}(E+V)$ 的时间复杂度。 -因而总的时间复杂度就有 $\mathcal{O}(E+V)$ +因而总的时间复杂度就有 $\mathcal{O}(E+V)$ ### 实现 @@ -109,8 +109,7 @@ bool toposort() { } ``` -时间复杂度:$O(n+m)$ -空间复杂度:$O(n)$ +时间复杂度: $O(n+m)$ 空间复杂度: $O(n)$ ### 合理性证明 @@ -118,6 +117,6 @@ bool toposort() { ## 参考 -1. 离散数学及其应用. ISBN:9787111555391 +1. 离散数学及其应用。ISBN:9787111555391 2. -3. Topological sorting, +3. Topological sorting, diff --git a/docs/graph/traverse.md b/docs/graph/traverse.md index e83b242f..5f1fcd16 100644 --- a/docs/graph/traverse.md +++ b/docs/graph/traverse.md @@ -1,4 +1,4 @@ -## 在树 / 图上 DFS +## 在树/图上 DFS 前置知识:[DFS 基础](/search/dfs) @@ -16,7 +16,7 @@ DFS 序列是指 DFS 调用过程中访问的节点编号的序列。 ### 括号序列 -DFS 进入某个节点的时候记录一个左括号 `(`,退出某个节点的啥时候记录一个右括号 `)`。 +DFS 进入某个节点的时候记录一个左括号 `(` ,退出某个节点的啥时候记录一个右括号 `)` 。 每个节点会出现两次。相邻两个节点的深度相差 1。 @@ -46,9 +46,9 @@ DFS 进入某个节点的时候记录一个左括号 `(`,退出某个节点的 注:树的 DFS 序列也是不唯一的。 -在 DFS 过程中,通过记录每个节点从哪个点访问而来,可以建立一个树结构,称为 DFS 树。 DFS 树是原图的一个生成树。 +在 DFS 过程中,通过记录每个节点从哪个点访问而来,可以建立一个树结构,称为 DFS 树。DFS 树是原图的一个生成树。 -DFS 树有很多性质,比如用来求 [强连通分量](/graph/scc) +DFS 树有很多性质,比如用来求[强连通分量](/graph/scc) ## BFS diff --git a/docs/graph/tree-basic.md b/docs/graph/tree-basic.md index eae34663..63820d92 100644 --- a/docs/graph/tree-basic.md +++ b/docs/graph/tree-basic.md @@ -76,12 +76,12 @@ 度数不超过 $1$ 的节点 -??? question " 为什么不是度为 $1$?" +??? question " 为什么不是度为 $1$ ?" 考虑 $n = 1$ 的时候。 #### 有根树 -没有子节点(度数为 $0$)的节点 +没有子节点(度数为 $0$ )的节点 ### 子树 @@ -93,19 +93,19 @@ 没有边 -$n = 1, m = 0$ + $n = 1, m = 0$ ### 链 -点 $i$ 的父亲为 $i - 1$。 +点 $i$ 的父亲为 $i - 1$ 。 -树的深度为$n - i$。 +树的深度为 $n - i$ 。 ### 菊花 所有非根节点的父亲均为根节点。 -树的深度为 $1$。 +树的深度为 $1$ 。 ### 二叉树 @@ -123,12 +123,12 @@ Complete binary tree,只有最下面两层节点的度数可以小于 2,且 ### 存父亲 -用一个数组记录每个节点的父亲节点:`fa`数组 +用一个数组记录每个节点的父亲节点: `fa` 数组 ### 邻接表 当成图来存 -有根树:`vector childs[n], fa[n]` +有根树: `vector childs[n], fa[n]` -二叉树:`int lch[n], rch[n], fa[n]` +二叉树: `int lch[n], rch[n], fa[n]` diff --git a/docs/graph/tree-misc.md b/docs/graph/tree-misc.md index 2207dce4..ceda6c87 100644 --- a/docs/graph/tree-misc.md +++ b/docs/graph/tree-misc.md @@ -4,7 +4,7 @@ 以这个点为根,那么所有的子树(不算整个树自身)的大小都不超过整个树大小的一半。 -找到一个点,其所有的子树中最大的子树节点数最少, 那么这个点就是这棵树的重心。 +找到一个点,其所有的子树中最大的子树节点数最少,那么这个点就是这棵树的重心。 删去重心后,生成的多棵树尽可能平衡。 @@ -20,7 +20,7 @@ 树的重心可以通过简单的两次搜索求出。 -1. 第一遍搜索求出每个结点的子结点数量 $sz[u]$ +1. 第一遍搜索求出每个结点的子结点数量 $sz[u]$ 2. 第二遍搜索找出使 $max\{sz[u],n-sz[u]-1\}$ 最小的结点。 实际上这两步操作可以在一次遍历中解决。对结点 u 的每一个儿子 v,递归的处理 v,求出 sz[v],然后判断是否是结点数最多的子树,处理完所有子结点后,判断 u 是否为重心。 diff --git a/docs/index.md b/docs/index.md index 21da9960..a6ac8c32 100644 --- a/docs/index.md +++ b/docs/index.md @@ -2,27 +2,27 @@ disqus: pagetime: title: OI Wiki -# 欢迎来到 **OI Wiki**![![GitHub watchers](https://img.shields.io/github/watchers/24OI/OI-Wiki.svg?style=social&label=Watch)](https://github.com/24OI/OI-wiki) [![GitHub stars](https://img.shields.io/github/stars/24OI/OI-Wiki.svg?style=social&label=Stars)](https://github.com/24OI/OI-wiki) +# 欢迎来到**OI Wiki**![![GitHub watchers](https://img.shields.io/github/watchers/24OI/OI-Wiki.svg?style=social&label=Watch)](https://github.com/24OI/OI-wiki)[![GitHub stars](https://img.shields.io/github/stars/24OI/OI-Wiki.svg?style=social&label=Stars)](https://github.com/24OI/OI-wiki) [![Word Art](./images/wordArt.png)](https://github.com/24OI/OI-wiki) -**OI** (Olympiad in Informatics,信息学奥林匹克竞赛)在中国起源于 1984 年,是五大高中学科竞赛之一。自 1989 年起,每年还会选拔出国家集训队选手准备 IOI (International Olympiad in Informatics,国际信息学奥林匹克竞赛)。 +**OI**(Olympiad in Informatics,信息学奥林匹克竞赛)在中国起源于 1984 年,是五大高中学科竞赛之一。自 1989 年起,每年还会选拔出国家集训队选手准备 IOI (International Olympiad in Informatics,国际信息学奥林匹克竞赛)。 -**ICPC** (International Collegiate Programming Contest, 国际大学生程序设计竞赛)由 ICPC 基金会(ICPC Foundation)举办,是最具影响力的大学生计算机竞赛。ICPC 主要分为区域赛(Regionals)和总决赛(World Finals)两部分。由于以前 ACM 赞助这个竞赛,也有很多人习惯叫它 ACM 竞赛。 +**ICPC**(International Collegiate Programming Contest, 国际大学生程序设计竞赛)由 ICPC 基金会(ICPC Foundation)举办,是最具影响力的大学生计算机竞赛。ICPC 主要分为区域赛(Regionals)和总决赛(World Finals)两部分。由于以前 ACM 赞助这个竞赛,也有很多人习惯叫它 ACM 竞赛。 -**OI Wiki** 致力于成为一个免费开放且持续更新的知识整合站点,大家可以在这里获取关于 **编程竞赛 (competitive programming)** 有趣又实用的知识,我们为大家准备了竞赛中的基础知识、常见题型、解题思路以及常用工具等内容,帮助大家更快速深入地学习编程竞赛。 +**OI Wiki**致力于成为一个免费开放且持续更新的知识整合站点,大家可以在这里获取关于**编程竞赛 (competitive programming)**有趣又实用的知识,我们为大家准备了竞赛中的基础知识、常见题型、解题思路以及常用工具等内容,帮助大家更快速深入地学习编程竞赛。 -本项目受 [CTF Wiki](https://ctf-wiki.github.io/ctf-wiki/) 的启发,在编写过程中参考了诸多资料,在此一并致谢。 +本项目受[CTF Wiki](https://ctf-wiki.github.io/ctf-wiki/)的启发,在编写过程中参考了诸多资料,在此一并致谢。 -本项目文档内容托管在 [GitHub](https://github.com/24OI/OI-wiki),主要使用 [Issues](https://github.com/24OI/OI-wiki/issues) / [QQ](https://jq.qq.com/?_wv=1027&k=5EfkM6K) / [Telegram](https://t.me/OIwiki) 进行交流沟通,期待你的加入。 +本项目文档内容托管在[GitHub](https://github.com/24OI/OI-wiki),主要使用[Issues](https://github.com/24OI/OI-wiki/issues)/[QQ](https://jq.qq.com/?_wv=1027&k=5EfkM6K)/[Telegram](https://t.me/OIwiki)进行交流沟通,期待你的加入。 -Telegram 群组链接为 [@OIwiki](https://t.me/OIwiki) , QQ 群号码为 [`588793226`](https://jq.qq.com/?_wv=1027&k=5EfkM6K),欢迎加入。 +Telegram 群组链接为[@OIwiki](https://t.me/OIwiki),QQ 群号码为[ `588793226` ](https://jq.qq.com/?_wv=1027&k=5EfkM6K),欢迎加入。 ## Material color palette 颜色主题 ### Primary colors 主色 -> 默认 `white` +> 默认 `white` 点击色块可更换主题的主色 @@ -61,7 +61,7 @@ Telegram 群组链接为 [@OIwiki](https://t.me/OIwiki) , QQ 群号码为 [`58 ### Accent colors 辅助色 -> 默认 `red` +> 默认 `red` 点击色块更换主题的辅助色 diff --git a/docs/intro/about.md b/docs/intro/about.md index 72e1134c..8efe310a 100644 --- a/docs/intro/about.md +++ b/docs/intro/about.md @@ -2,33 +2,33 @@ Q:你们是为什么想要做这个 Wiki 的呢? -A:不知道你在学 **OI** 的时候,面对庞大的知识体系,有没有感到过迷茫无助的时候?**OI Wiki** 想要做的事情可能类似于 “让更多竞赛资源不充裕的同学能方便地接触到训练资源”。当然这么表述也不完全,做 Wiki 的动机可能也很纯粹,只是简单地想要对 **OI** 的发展做出一点点微小的贡献吧。XD +A:不知道你在学**OI**的时候,面对庞大的知识体系,有没有感到过迷茫无助的时候?**OI Wiki**想要做的事情可能类似于“让更多竞赛资源不充裕的同学能方便地接触到训练资源”。当然这么表述也不完全,做 Wiki 的动机可能也很纯粹,只是简单地想要对**OI**的发展做出一点点微小的贡献吧。XD Q:我很感兴趣,怎么参与呢? -A:**OI Wiki** 现在托管在 [GitHub](https://github.com/24OI/OI-wiki) 上,你可以直接访问这个 [repo](https://github.com/24OI/OI-wiki) 来查看最新进展。参与的途径包括在 [GitHub](https://github.com/24OI/OI-wiki) 上面开 Issue、Pull Request,或者在交流群中分享你的想法、直接向管理员投稿。目前,我们使用的框架是 [mkdocs](https://mkdocs.readthedocs.io),支持 Markdown 格式(也支持插入数学公式)。 +A:**OI Wiki**现在托管在[GitHub](https://github.com/24OI/OI-wiki)上,你可以直接访问这个[repo](https://github.com/24OI/OI-wiki)来查看最新进展。参与的途径包括在[GitHub](https://github.com/24OI/OI-wiki)上面开 Issue、Pull Request,或者在交流群中分享你的想法、直接向管理员投稿。目前,我们使用的框架是[mkdocs](https://mkdocs.readthedocs.io),支持 Markdown 格式(也支持插入数学公式)。 -Q:可是我比较弱…… 不知道我能做点什么? +Q:可是我比较弱……不知道我能做点什么? -A:一切源于热爱。你可以协助其他人审核修改稿件,帮助我们宣传 **OI Wiki** ,为社区营造良好学习交流氛围! +A:一切源于热爱。你可以协助其他人审核修改稿件,帮助我们宣传**OI Wiki**,为社区营造良好学习交流氛围! Q:现在主要是谁在做这件事啊?感觉这是个大坑,真的能做好吗? -A:最开始主要是一些退役老年选手在做这件事,后来遇到了很多志同道合的小伙伴:有现役选手,退役玩家,也有从未参加过 **OI** 的朋友。目前,这个项目主要是由 **OI Wiki** Team 来维护。(下面是一张合影) +A:最开始主要是一些退役老年选手在做这件事,后来遇到了很多志同道合的小伙伴:有现役选手,退役玩家,也有从未参加过**OI**的朋友。目前,这个项目主要是由**OI Wiki**Team 来维护。(下面是一张合影) -当然,这个项目只靠我们的力量是很难做得十全十美的,我们诚挚地邀请你一起来完善 **OI Wiki**。 +当然,这个项目只靠我们的力量是很难做得十全十美的,我们诚挚地邀请你一起来完善**OI Wiki**。 -Q:听说过 nocow 吧,**OI Wiki** 怎么保证我们添加的内容不会像 nocow 那样突然间就不见了呢? +Q:听说过 nocow 吧,**OI Wiki**怎么保证我们添加的内容不会像 nocow 那样突然间就不见了呢? -A:我们把内容托管在 [GitHub](https://github.com/24OI/OI-wiki) 上面,即使我们的服务器翻车了,内容也不会丢失。另外,我们也会定期备份大家的心血,即使有一天 [GitHub](https://github.com/24OI/OI-wiki) 倒闭了(?),我们的内容也不会丢失。 +A:我们把内容托管在[GitHub](https://github.com/24OI/OI-wiki)上面,即使我们的服务器翻车了,内容也不会丢失。另外,我们也会定期备份大家的心血,即使有一天[GitHub](https://github.com/24OI/OI-wiki)倒闭了(?),我们的内容也不会丢失。 -Q:**OI Wiki** 好像现在大部分内容都是空的啊? +Q:**OI Wiki**好像现在大部分内容都是空的啊? -A:是的,目前进度只完成了 73% (重新统计于 2018.11),还远远称不上是一个合格的 Wiki。在过去的几个月里,主要添加的内容也比较基础。所以在这里进行征稿和招募,希望可以遇到有同样想法的朋友,我们一起把 **OI Wiki** 完善起来。 +A:是的,目前进度只完成了 73%(重新统计于 2018.11),还远远称不上是一个合格的 Wiki。在过去的几个月里,主要添加的内容也比较基础。所以在这里进行征稿和招募,希望可以遇到有同样想法的朋友,我们一起把**OI Wiki**完善起来。 -Q:为什么不直接去写 [中文维基](https://zh.wikipedia.org/) 呢? +Q:为什么不直接去写[中文维基](https://zh.wikipedia.org/)呢? A:因为我们希望可以真正帮到更多的选手或者对这些内容感兴趣的人,由于众所周知的原因,中文维基上的内容并不是无门槛就可以获取到的。 @@ -36,6 +36,6 @@ A:因为我们希望可以真正帮到更多的选手或者对这些内容感 感谢你看到了最后,我们现在急需的,就是你的帮助。 -**OI Wiki** Team +**OI Wiki**Team 2018.8 diff --git a/docs/intro/common-mistakes.md b/docs/intro/common-mistakes.md index 2abf587b..306a8acc 100644 --- a/docs/intro/common-mistakes.md +++ b/docs/intro/common-mistakes.md @@ -1,25 +1,25 @@ -本页面主要分享一下在竞赛中经常 / 很多人会出现的错误。 +本页面主要分享一下在竞赛中经常/很多人会出现的错误。 1. 由于运算符优先级产生的错误。 - - `1 << 1+1` : 1 左移了 2,即该表达式返回的值是 `4`。 + - `1 << 1+1` : 1 左移了 2,即该表达式返回的值是 `4` 。 - 由于宏的展开,且未加括号导致的错误: ```cpp #define pwr(x) x* x pwr(2 + 2) ``` - 该宏返回的值并非 $4^2 = 16$ 而是 $2+2\times 2+2 = 8$。 + 该宏返回的值并非 $4^2 = 16$ 而是 $2+2\times 2+2 = 8$ 。 2. 文件操作有可能会发生的错误。 - - 对拍时未清除文件指针即 `fclose(fp)` 就又令 `fp = fopen()`, 这会使得进程出现大量的文件野指针。 - - `freopen()` 中的文件名未加 `.in`/`.out`。 + - 对拍时未清除文件指针即 `fclose(fp)` 就又令 `fp = fopen()` , 这会使得进程出现大量的文件野指针。 + - `freopen()` 中的文件名未加 `.in` / `.out` 。 -3. `int mian()`。 +3. `int mian()` 。 4. 无向图边表未开 2 倍。 5. 多组数据未清空数组。 -6. 输出 `double` 要使用 `%f` 而非 `%lf`。 参考 [链接](https://stackoverflow.com/questions/4264127/correct-format-specifier-for-double-in-printf)。 +6. 输出 `double` 要使用 `%f` 而非 `%lf` 。参考[链接](https://stackoverflow.com/questions/4264127/correct-format-specifier-for-double-in-printf)。 7. 分治未判边界导致死递归。 @@ -27,10 +27,9 @@ 9. 不正确地使用 `static` 修饰符。 -10. `-1 >> 1 == 1`。 +10. `-1 >> 1 == 1` 。 -11. 不正确地使用宏。 - `#define min(x,y) xEsc 啦。但是,Vim 的插入与普通模式切换异常频繁,而 Esc 又太远了,有什么办法呢?Vim 还提供了 Ctrl + \[ 的快捷键来返回普通模式,是否近多了呢? +而如何返回普通模式?当然是Esc啦。但是,Vim 的插入与普通模式切换异常频繁,而Esc又太远了,有什么办法呢?Vim 还提供了Ctrl+\[的快捷键来返回普通模式,是否近多了呢? -虽说能够熟练了后,切换模式不再是问题,但是其实有的时候我们只是需要进入普通模式下按一次小命令,来回切换又显得浪费了一点点时间。而 Vim 又提供了插入 - 普通模式来避免这一尴尬的问题。在插入模式下,只需要按 Ctrl + o 即可进入此模式,当进行完一次操作后又会自动回到插入模式。这样岂不是更省时间? +虽说能够熟练了后,切换模式不再是问题,但是其实有的时候我们只是需要进入普通模式下按一次小命令,来回切换又显得浪费了一点点时间。而 Vim 又提供了插入 - 普通模式来避免这一尴尬的问题。在插入模式下,只需要按Ctrl+o即可进入此模式,当进行完一次操作后又会自动回到插入模式。这样岂不是更省时间? #### 普通模式 (normal) @@ -121,13 +121,13 @@ Vim 的命令大部分都是在普通模式下完成的,普通模式下可不 其实大多数编辑器都是用方向键做出移动命令,Vim 也不例外,但 `hjkl` 给了我们更好的选择,只需要一段时间的适应,你便能快速地操作它们进行移动,而且它们可没有方向键那么远,节省时间是一流的。 -普通模式下最重要的命令,没有之一,那就是 `u`。撤销命令,作用是撤销上一次对文本的更改,普通模式下的 `x`,`d`,`p` 命令都会被撤销,同时进入一次插入模式所编辑的文本也算一次更改,`u` 命令会删去从进入到退出插入模式所输入的所有东西。与之对应的是 Ctrl + r 命令,他的作用是撤销上次的撤销命令,相当于大部分 windows 下程序中的 Ctrl + y。 +普通模式下最重要的命令,没有之一,那就是 `u` 。撤销命令,作用是撤销上一次对文本的更改,普通模式下的 `x` , `d` , `p` 命令都会被撤销,同时进入一次插入模式所编辑的文本也算一次更改, `u` 命令会删去从进入到退出插入模式所输入的所有东西。与之对应的是Ctrl+r命令,他的作用是撤销上次的撤销命令,相当于大部分 windows 下程序中的Ctrl+y。 -然后的话, 就是普通模式下常用的命令。由于对行命令的使用很频繁,所以大部分的单键命令都可以通过按两次来实现对行操作。常用命令是 `x` ,用于删除光标后的一个字符。然后是 `d` 命令,也是删除,但是种类更多,这里不做赘述。同时 `d` 命令像之前说的,按两次即可删除整行,即 `dd`。 +然后的话,就是普通模式下常用的命令。由于对行命令的使用很频繁,所以大部分的单键命令都可以通过按两次来实现对行操作。常用命令是 `x` ,用于删除光标后的一个字符。然后是 `d` 命令,也是删除,但是种类更多,这里不做赘述。同时 `d` 命令像之前说的,按两次即可删除整行,即 `dd` 。 -然后是`y`命令,可以复制被选中的区域,这涉及到可视模式,即按 `v` 进入可视模式,多用于选中区域。进入可视模式后移动光标来确定选取范围是可以的,此时按 `o` 键即可切换活动端,省去了如果跑反方向的麻烦。当然,我相信很多人还是习惯用鼠标操作这一过程的,包括移动光标。Vim 很温馨的提供了这一配置:`set mouse=a`。你可以将它写入你的配置文件中去。有了它之后,你将能够用鼠标选中区域并进行复制操作。当然,选中后按 `x` 或 `d` 亦可删除。同时,`y` 也符合 `d` 的性质,`yy` 将可以复制当前行。 +然后是 `y` 命令,可以复制被选中的区域,这涉及到可视模式,即按 `v` 进入可视模式,多用于选中区域。进入可视模式后移动光标来确定选取范围是可以的,此时按 `o` 键即可切换活动端,省去了如果跑反方向的麻烦。当然,我相信很多人还是习惯用鼠标操作这一过程的,包括移动光标。Vim 很温馨的提供了这一配置: `set mouse=a` 。你可以将它写入你的配置文件中去。有了它之后,你将能够用鼠标选中区域并进行复制操作。当然,选中后按 `x` 或 `d` 亦可删除。同时, `y` 也符合 `d` 的性质, `yy` 将可以复制当前行。 -然后就是更快的跳跃了。如果说只是使用 `hjkl` 的话,光标的移动显然不够快,而鼠标却又要伸手去拿。Vim 提供了普通模式下更快的跳跃方法,`w` 可以跳到下个单词的开头,而 `e` 可以跳到当前单词结尾,`0` 可以跳至行首,`$` 可以跳至行尾,岂不是快多了?而且 `w`,`e`,`0`,`$` 还可以用于许多命令中 `de`,`dw`,`d0`,`d&` 分别对应删至单词尾,删至下个单词头,删至行首,删至行尾。以及`y`命令亦可同理。 +然后就是更快的跳跃了。如果说只是使用 `hjkl` 的话,光标的移动显然不够快,而鼠标却又要伸手去拿。Vim 提供了普通模式下更快的跳跃方法, `w` 可以跳到下个单词的开头,而 `e` 可以跳到当前单词结尾, `0` 可以跳至行首, `$` 可以跳至行尾,岂不是快多了?而且 `w` , `e` , `0` , `$` 还可以用于许多命令中 `de` , `dw` , `d0` , `d&` 分别对应删至单词尾,删至下个单词头,删至行首,删至行尾。以及 `y` 命令亦可同理。 然后是 Vim 的可重复。在输入某个命令前,输入一个数字的话,就会重复那么多次。如在普通模式下: @@ -137,7 +137,7 @@ asdadasdddd asdasdasd ``` -光标正位于第一行,该如何删除这三行呢?普通模式下按 `3 dd` 即可。其实还有`.`命令也是可以做到一些重复的,这会在效率篇中提到。 +光标正位于第一行,该如何删除这三行呢?普通模式下按 `3 dd` 即可。其实还有 `.` 命令也是可以做到一些重复的,这会在效率篇中提到。 然后是全文的跳跃,按 `gg` 可跳至代码的开头,按 `G` 可跳至代码最后一行,先按数字再按 `G` 可跳至指定行。 @@ -149,13 +149,13 @@ asdasdasd 其实这并不能称作是一个模式 = =。 -普通模式下只需要按 : 下方就会蹦出命令框框,输入相关命令即可。如 Vim 在线帮助文档,输入 `:help` 即可,如果看不懂英文…… 请下载 Vim 用户手册中文,或者移步插件篇。 +普通模式下只需要按 : 下方就会蹦出命令框框,输入相关命令即可。如 Vim 在线帮助文档,输入 `:help` 即可,如果看不懂英文……请下载 Vim 用户手册中文,或者移步插件篇。 此模式下有一些很有用的命令 -`:q` 退出,`:w` 保存,`:wq` 保存并退出,`:q!` 不保存并退出,`:e filename` 打开当前目录下指定文件,这些是比较基础的。 + `:q` 退出, `:w` 保存, `:wq` 保存并退出, `:q!` 不保存并退出, `:e filename` 打开当前目录下指定文件,这些是比较基础的。 -然后是很强大的命令 `:x1,x2 s/A 串 / B 串 /` 作用是把第 `x1` 行至 `x2` 行中的所有 A 串替换成 B 串。想象一下题写完了,但是发现没开 `long long` 的时候,完全不绝望有没有,一个小命令,妙不可言。瞬间所有 `int` 变 `long long`。 +然后是很强大的命令 `:x1,x2 s/A 串/B 串/` 作用是把第 `x1` 行至 `x2` 行中的所有 A 串替换成 B 串。想象一下题写完了,但是发现没开 `long long` 的时候,完全不绝望有没有,一个小命令,妙不可言。瞬间所有 `int` 变 `long long` 。 以上都是 Vim 内部的命令,但是实际上如果命令形式是 `:! 命令` 那么就将在外部执行命令,即是以 bash 终端执行命令。既然都是外部 bash 了我就不多做介绍了,那块地不归我管…… @@ -163,11 +163,11 @@ asdasdasd 可视模式的作用总结起来大概就是选中高亮,但是块状的可视模式可以干更多的事情,不过太麻烦了,对于新人来说大概会脑阔疼。 -普通模式下按 `v` 即可进入可视模式,`hjkl` 可以移动高亮选区某一头,如果发现反了或者你进入可视模式的时候是在想要选中区域的中间位置,不用急着退出重进,更不用花时间又移回去,只需要按`o`即可切换活动端,操作高亮选区的另一头。或者用鼠标也不是不行啦…… +普通模式下按 `v` 即可进入可视模式, `hjkl` 可以移动高亮选区某一头,如果发现反了或者你进入可视模式的时候是在想要选中区域的中间位置,不用急着退出重进,更不用花时间又移回去,只需要按 `o` 即可切换活动端,操作高亮选区的另一头。或者用鼠标也不是不行啦…… 用鼠标选中高亮选区当然也可以说是进入可视模式的办法之一。 -然后就是`y`或者`d`操作,没了 QwQ。 +然后就是 `y` 或者 `d` 操作,没了 QwQ。 emm 基础应该就用到这些了吧,往后的插件,配置,更多操作在对应篇幅里。 @@ -185,8 +185,8 @@ vimtutor #### 从缩小控制区域开始 -为什么 Emacs 和 Vim 这些编辑器效率高?很重要的一点在于这些编辑器可以让你切掉你的右半部分的键盘而让你的双手始终处于主键盘区域, 并且让你的双手保持合作, 而不会出现一只手不停的按而另一只手摊在键盘上。 -所以, 如果你想用好 Vim , 不要去按方向键,不要去碰鼠标,建议使用这几行丧心病狂的配置: +为什么 Emacs 和 Vim 这些编辑器效率高?很重要的一点在于这些编辑器可以让你切掉你的右半部分的键盘而让你的双手始终处于主键盘区域,并且让你的双手保持合作,而不会出现一只手不停的按而另一只手摊在键盘上。 +所以,如果你想用好 Vim , 不要去按方向键,不要去碰鼠标,建议使用这几行丧心病狂的配置: ```vim " 使方向键失效 @@ -198,19 +198,19 @@ imap 只需要最多一周的时间,你将完全适应 hjkl 的移动并感慨其效率之高。 -但也许这还不够, 你还可以进一步缩小你双手需要控制的区域。 +但也许这还不够,你还可以进一步缩小你双手需要控制的区域。 -- Backspace (删除键) 使用十分频繁, 但它处在主键盘的角落, 你不得不挪开手或是伸长小拇指。 - 但在 Vim (甚至终端) 里, 你可以用 ctrl+h 来彻底代替 Backspace。 -- 回车键使用同样频繁, 但同样不挪一挪手就得伸长小拇指。 - 幸运的是在 Vim 和终端中, ctrl+m 完全等效与回车。 -- 在绝大多数的情况下, 不要去按右边的 ctrl, shift, 用左边的代替。 +- Backspace(删除键)使用十分频繁,但它处在主键盘的角落,你不得不挪开手或是伸长小拇指。 + 但在 Vim(甚至终端)里,你可以用 ctrl+h 来彻底代替Backspace。 +- 回车键使用同样频繁,但同样不挪一挪手就得伸长小拇指。 + 幸运的是在 Vim 和终端中,ctrl+m 完全等效与回车。 +- 在绝大多数的情况下,不要去按右边的 ctrl, shift, 用左边的代替。 #### 违背常理的改变 Vim​ 本便是一个与寻常 IDE 不同的编辑器。那么同样你可以为它做出不寻常的改变。 -如果长久使用可以发现,Esc 键太远了,小拇指都按得不顺手。那么我们该怎么处理呢?诶,我又不小心碰到大小写锁定切换键了!你会发现,CapsLock按键实在太没用了 O.O ,不仅难用到,而且这么顺手这么近,还容易错按到,我要它何用?我为什么不吧它和 Esc 换一下呢?皆大欢喜呀。对吗? +如果长久使用可以发现,Esc 键太远了,小拇指都按得不顺手。那么我们该怎么处理呢?诶,我又不小心碰到大小写锁定切换键了!你会发现,CapsLock按键实在太没用了 O.O,不仅难用到,而且这么顺手这么近,还容易错按到,我要它何用?我为什么不吧它和Esc换一下呢?皆大欢喜呀。对吗? 置换方案如下: @@ -220,33 +220,33 @@ Vim​ 本便是一个与寻常 IDE 不同的编辑器。那么同样你可以 sudo vim /usr/share/X11/xkb/symbols/pc ``` -找到`key ` 与 `key ` 这两行,并调换两行的中括号 `[]` 中内容并注销重新进入系统,你就会发现它们换过来了!!开始可能会极度极度懵逼,不大回得过神来,但是习惯了几天后,你就会~~真香~~接受了,这个时候你就会进一步享受 Vim​ 的模式,高速切换模式不是梦。 +找到 `key ` 与 `key ` 这两行,并调换两行的中括号 `[]` 中内容并注销重新进入系统,你就会发现它们换过来了!开始可能会极度极度懵逼,不大回得过神来,但是习惯了几天后,你就会~~真香~~接受了,这个时候你就会进一步享受 Vim​ 的模式,高速切换模式不是梦。 -当然,你可能要问了,如果是在考场怎么办?没有 sudo 权限改不了啊。不用担心,还有办法,但是这个办法重启就失效了,所以考场就用这个办法 (\\OvO/) 。在终端输入如下语句即可哦 (考场亲测): +当然,你可能要问了,如果是在考场怎么办?没有 sudo 权限改不了啊。不用担心,还有办法,但是这个办法重启就失效了,所以考场就用这个办法 (\\OvO/)。在终端输入如下语句即可哦(考场亲测): ```bash xmodmap -e 'clear Lock' -e 'keycode x042=Escape' ``` -当你能做到高速切换模式与熟练使用 hjkl 移动时,仅在想敲东西的时候进入插入模式并且敲完之后即刻推出就成了一种本能。其实你会发现 o 超级好用。现在很多时候我的换行并不是插入模式下的回车键,而是` o`。而这也有另一个好处。这将会在 **分块撤销** 中得到体现。 +当你能做到高速切换模式与熟练使用 hjkl 移动时,仅在想敲东西的时候进入插入模式并且敲完之后即刻推出就成了一种本能。其实你会发现o超级好用。现在很多时候我的换行并不是插入模式下的回车键,而是 ` o` 。而这也有另一个好处。这将会在**分块撤销**中得到体现。 #### 重复!效率!重复! -毫无疑问,对动作的重复是提高效率最直接的办法,也是对效率最直接的反映。接下来我将依次介绍 Vim 的.命令,简单的录制与重复宏与`normal`命令。 +毫无疑问,对动作的重复是提高效率最直接的办法,也是对效率最直接的反映。接下来我将依次介绍 Vim 的.命令,简单的录制与重复宏与 `normal` 命令。 ##### 自行车 -- . 命令 -当你使用 Vim 的时候,对于重复的文本修改其实内心是绝望的,因为 Vim 注定比其他编辑器会多出两个键的按键 -- Esc与i。就像走路的时候别人都是迈步子,但是你一定要跳一下再走一步,这种重复令人十分烦躁与无奈。这可以说是一个致命的缺点,但是,Vim 其实为我们提供了一台 “自行车” -- `.`命令。它令我们能直接不用迈步落步,只要往下踩就好了。 +当你使用 Vim 的时候,对于重复的文本修改其实内心是绝望的,因为 Vim 注定比其他编辑器会多出两个键的按键 --Esc与i。就像走路的时候别人都是迈步子,但是你一定要跳一下再走一步,这种重复令人十分烦躁与无奈。这可以说是一个致命的缺点,但是,Vim 其实为我们提供了一台“自行车”-- `.` 命令。它令我们能直接不用迈步落步,只要往下踩就好了。 -要说起这个命令,其实效果也很简单,那就是重复上次执行的命令。但是,看似简单的外表,其实却是无比的强大,重点就看你如何使用它了。这个重复上次的命令,其实可以是`数字 + 命令`的组合,同时,`进入插入模式 + 输入文本 + Esc`也是一个命令的范畴。那么它的用处就大起来了 +要说起这个命令,其实效果也很简单,那就是重复上次执行的命令。但是,看似简单的外表,其实却是无比的强大,重点就看你如何使用它了。这个重复上次的命令,其实可以是 `数字 + 命令` 的组合,同时, `进入插入模式 + 输入文本 + Esc` 也是一个命令的范畴。那么它的用处就大起来了 -Vim 的宗旨是效率至上唯快不破,那么一切都以`快`来思考才是正途。那么如何使用这个自行车才是最快的呢?当然是沿着起点和终点之间的那条直线骑。同样的,.命令只有用到关键处才是最快的。如何使用它呢?我们来看几个例子。 +Vim 的宗旨是效率至上唯快不破,那么一切都以 `快` 来思考才是正途。那么如何使用这个自行车才是最快的呢?当然是沿着起点和终点之间的那条直线骑。同样的,.命令只有用到关键处才是最快的。如何使用它呢?我们来看几个例子。 ```cpp int a, b cin >> a >> b cout << a + b return 0 ``` -可以看见每一行都少了空格。那么如何使用.命令来优雅地修改呢?为了省去 Vim 的缺点,我们显然可以先将光标移到第一行行尾,然后输入`a;`,然后接着向下移动到每一行行尾的时候,直接使用.命令即可。但是这还是没有变快。那么怎么办呢?你会想起 Vim 还有个进入插入模式的命令:A 。即移动到行尾插入。那么一切都鲜明起来了,命令如下即可:`A;`,然后你只需要不停地`j.`就行了,还是挺~~爽~~方便的。 +可以看见每一行都少了空格。那么如何使用.命令来优雅地修改呢?为了省去 Vim 的缺点,我们显然可以先将光标移到第一行行尾,然后输入 `a;` ,然后接着向下移动到每一行行尾的时候,直接使用.命令即可。但是这还是没有变快。那么怎么办呢?你会想起 Vim 还有个进入插入模式的命令:A。即移动到行尾插入。那么一切都鲜明起来了,命令如下即可: `A;` ,然后你只需要不停地 `j.` 就行了,还是挺~~爽~~方便的。 那么它的用处止步于此了吗?显然远远没有。再来看如下一行代码: @@ -257,21 +257,21 @@ int check() { } ``` -后面五个赋值语句的数组名写错了肿么办?一个个改显然太坑了,而命令行模式的`s`命令又会**全部改掉**,那么怎么办呢?可以回忆一下我们普通模式下的`s`命令,它的作用是删除光标处字符并进入插入模式。那么怎么使用呢?来到第一个错误的数组名首字母处,按下`4s`,你会发现,数组名四个字符统统消失啦!然后输入正确的数组名再退出即可。之后?一个个把光标移过去再使用.就好啦。 +后面五个赋值语句的数组名写错了肿么办?一个个改显然太坑了,而命令行模式的 `s` 命令又会**全部改掉**,那么怎么办呢?可以回忆一下我们普通模式下的 `s` 命令,它的作用是删除光标处字符并进入插入模式。那么怎么使用呢?来到第一个错误的数组名首字母处,按下 `4s` ,你会发现,数组名四个字符统统消失啦!然后输入正确的数组名再退出即可。之后?一个个把光标移过去再使用.就好啦。 -**但是**,这还不够优雅,移动光标太浪费时间了!那么有什么办法嘛?答案是显然的。我们可以使用查找模式!`/book`再按下回车,并使用`n`键来到第四个数组名处,然后`4s 新数组名 `,接着我们只需要重复`n.`就行了 (O#O),是不是灰常优雅! +**但是**,这还不够优雅,移动光标太浪费时间了!那么有什么办法嘛?答案是显然的。我们可以使用查找模式! `/book` 再按下回车,并使用 `n` 键来到第四个数组名处,然后 `4s 新数组名 ` ,接着我们只需要重复 `n.` 就行了 (O#O),是不是灰常优雅! -**再但是**,其实如果你开启了搜索结果高亮的选项,查找模式其实蛮难看的,这里我们引入一个新的轻便型查找命令 -- `f`!使用很简单,在一行中普通模式下,`f + 单个字符`即可查找此行中出现的这个字符并将光标移至字符处,按`;`下一个,`,`上一个。那么无需查找模式了,我们只需`fb;;;`之后进入插入模式修改,然后`;.`即可!所以这么说,对于行内移动,其实最常用的一般就几个命令:`h,l,0,$,f`,而且`$`命令是可以被`f`替代的,那就是`f;` 。所以.是不是炒鸡妙呀 (_ OvO _)。 +**再但是**,其实如果你开启了搜索结果高亮的选项,查找模式其实蛮难看的,这里我们引入一个新的轻便型查找命令 -- `f` !使用很简单,在一行中普通模式下, `f + 单个字符` 即可查找此行中出现的这个字符并将光标移至字符处,按 `;` 下一个, `,` 上一个。那么无需查找模式了,我们只需 `fb;;;` 之后进入插入模式修改,然后 `;.` 即可!所以这么说,对于行内移动,其实最常用的一般就几个命令: `h,l,0,$,f` ,而且 `$` 命令是可以被 `f` 替代的,那就是 `f;` 。所以.是不是炒鸡妙呀 (_OvO_)。 做个总结:.命令适用于重复的添加,修改,删除文本。就像一个自行车,踩起来蹭蹭的 = =。 ##### 飞机 -- 宏 -Vim 的宏功能极度可怕,它重复的可不只是一个命令,你想要指定重复多长的命令它都可以的。那么如何使用它呢?首先我们要先 “录制” -- 顾名思义,把一串按键操作录下来再回放,就达到了重复的效果。 +Vim 的宏功能极度可怕,它重复的可不只是一个命令,你想要指定重复多长的命令它都可以的。那么如何使用它呢?首先我们要先“录制”-- 顾名思义,把一串按键操作录下来再回放,就达到了重复的效果。 录制的方法很简单,处在普通模式下先按 `q` 键,告诉 Vim​ 你要开始了。然后,宏的按键录制就像复制粘贴的内容一样是需要一个存放之处的,那么下一步就是为它指定存放处,你可以按下 26 个字母中的任意一个来指定,然后左下方会显示 `记录中 @你刚刚选择的字母` 然后你就可以开始快乐地录制了。同理普通模式下按 `q` 暂停录制。 -那么如何调用呢?很简单,按下 `:` 进入命令行模式,命令为 `@选择的记录字母` 然后之前录制的按键就被调用了...... 听起来是不是麻烦没用?当然不是,你忘了 . 命令不成,执行一遍宏之后使用 . 命令不就可以不这么麻烦了?当然,如果这些所有重复技巧在一起就很强大了 --- 录制宏 --> 调用宏 --> . 命令重复 --> 数字 +. 命令达到无上效果。 +那么如何调用呢?很简单,按下 `:` 进入命令行模式,命令为 `@选择的记录字母` 然后之前录制的按键就被调用了……听起来是不是麻烦没用?当然不是,你忘了.命令不成,执行一遍宏之后使用.命令不就可以不这么麻烦了?当然,如果这些所有重复技巧在一起就很强大了 --- 录制宏 --> 调用宏 -->.命令重复 --> 数字 +.命令达到无上效果。 岂不快哉? @@ -279,19 +279,19 @@ Vim 的宏功能极度可怕,它重复的可不只是一个命令,你想要 顾名思义,自然有关 normal 模式,效果还是重复,但是 --- 这个命令可以选择行。 -如何使用呢?`:` 进入命令行模式,命令如下: +如何使用呢? `:` 进入命令行模式,命令如下: ```vim :a,b normal 命令 ``` -命令作用是在 a~b 行上, normal 模式下,执行 normal 后面的那串命令。或许看起来还不是特别强大,但是作为一个可以重复 . 命令的工具以及其便于理解性,它的使用频率甚至更高于宏。 +命令作用是在 a~b 行上,normal 模式下,执行 normal 后面的那串命令。或许看起来还不是特别强大,但是作为一个可以重复.命令的工具以及其便于理解性,它的使用频率甚至更高于宏。 ##### 数字 + . + 宏 + normal 当命令们抱团的时候,它们才是最强大的,而绝非各自为战。我们演示一个日常即可使用的方法吧。 -打个比方,我下载了一本书,我需要它的每一个章节都变成`标题` --- 对,就是 markdown 里的那种 --- 以方便转换成 mobi 之类的格式或者方便生成 TOC 目录跳转,怎么办呢?几千个章节哪里好处理哇。那么重复命令就大大的有用了。 +打个比方,我下载了一本书,我需要它的每一个章节都变成 `标题` --- 对,就是 markdown 里的那种 --- 以方便转换成 mobi 之类的格式或者方便生成 TOC 目录跳转,怎么办呢?几千个章节哪里好处理哇。那么重复命令就大大的有用了。 1 /正则表达式 进行查找(搜索章节名的表达式请务必上网搜一下) 2 q字母 开始录制宏 @@ -301,7 +301,7 @@ Vim 的宏功能极度可怕,它重复的可不只是一个命令,你想要 完成了!快吗? -重复命令的介绍到此结束 (甚至还超纲了) +重复命令的介绍到此结束(甚至还超纲了) ### 插件篇 @@ -331,9 +331,9 @@ git clone https://github.com/VundleVim/Vundle.vim.git ~/.vim/bundle/Vundle.vim 就安装好了。 -然后在 .vim 文件夹下创建文件夹 plugin 。这个文件夹用于存放那种不能用 Vundle 插件下载,而在别的地方有得下载的脚本插件,名字是 xxx.vim,直接扔进这个文件夹就可以使用了。 +然后在 .vim 文件夹下创建文件夹 plugin。这个文件夹用于存放那种不能用 Vundle 插件下载,而在别的地方有得下载的脚本插件,名字是 xxx.vim,直接扔进这个文件夹就可以使用了。 -Vundle 可以很轻松的管理插件,只需要在配置中写一下,并在 Vim 中执行`:PluginInstall`命令,就可以自动从 github 上拉取插件,当然也拉取不了 github 上没有的 = =。而如果不想用了什么插件也无须删去,在配置中注释掉那个插件的相关就行了。具体配置请移步配置篇,此处将会详细介绍我的各个插件。 +Vundle 可以很轻松的管理插件,只需要在配置中写一下,并在 Vim 中执行 `:PluginInstall` 命令,就可以自动从 github 上拉取插件,当然也拉取不了 github 上没有的 = =。而如果不想用了什么插件也无须删去,在配置中注释掉那个插件的相关就行了。具体配置请移步配置篇,此处将会详细介绍我的各个插件。 #### 文件管理 @@ -345,15 +345,15 @@ vim filename 打开文件还是在 Vim 内使用 `:e filename` 来打开文件显然都过于麻烦。那么有没有什么更好的法子? -答案是显然的,Vim 的用户们开发了 nerdtree 这一插件。这个插件达到了一种类似于 VScode 中的效果——工程目录树,之需在左侧目录栏选中相应文件即可打开相应文件。这在配置篇中将会有介绍。nerdtree 的开启方式是在 Vim 中输入 `:NERDTreeToggle` ,它会在左侧打开一个侧边栏窗口。我知道这显然太过麻烦,所以在配置中我给它赋予了 F10 这个快捷键。至于具体还有什么快捷键,详请参照 [此文章]([http://yang3wei.github.io/blog/2013/01/29/nerdtree-kuai-jie-jian-ji-lu/)。 +答案是显然的,Vim 的用户们开发了 nerdtree 这一插件。这个插件达到了一种类似于 VScode 中的效果——工程目录树,之需在左侧目录栏选中相应文件即可打开相应文件。这在配置篇中将会有介绍。nerdtree 的开启方式是在 Vim 中输入 `:NERDTreeToggle` ,它会在左侧打开一个侧边栏窗口。我知道这显然太过麻烦,所以在配置中我给它赋予了F10这个快捷键。至于具体还有什么快捷键,详请参照[此文章]([http://yang3wei.github.io/blog/2013/01/29/nerdtree-kuai-jie-jian-ji-lu/)。 -也许有人要说考场上该如何呢?没关系,Vim 自带了一个稍逊一筹的文件管理器 netrw 。如果你的命令是这样的 +也许有人要说考场上该如何呢?没关系,Vim 自带了一个稍逊一筹的文件管理器 netrw。如果你的命令是这样的 ```shell vim 文件夹(或者说目录)路径 ``` -或者是在 Vim 中 `e 文件夹路径`即可打开目录插件,你可以亲手试一试,我觉得这个还是不难琢磨的。同时在上述两个命令中可以用`.`来表示当前工作目录,意思是可以用 +或者是在 Vim 中 `e 文件夹路径` 即可打开目录插件,你可以亲手试一试,我觉得这个还是不难琢磨的。同时在上述两个命令中可以用 `.` 来表示当前工作目录,意思是可以用 ```shell vim . @@ -377,49 +377,49 @@ set autochdir ![airline2](./images/airline2.png) -那么然后呢?字体是可以在终端中设置的,Gvim 中更是有一个图形化的菜单。再就是主题了,它掌管着语法高亮的色彩,背景颜色等等。我个人推荐一个主题,那就是 onedark 。附图一张。 +那么然后呢?字体是可以在终端中设置的,Gvim 中更是有一个图形化的菜单。再就是主题了,它掌管着语法高亮的色彩,背景颜色等等。我个人推荐一个主题,那就是 onedark。附图一张。 ![onedarktheme](./images/onedarktheme.png) -使用主题的方法呢,就是在 `.vim` 文件夹下建立 `colors` 文件夹,再将主题文件放入。其后缀名为 `.vim` ,如:`onedark.vim`。具体配置中怎么写,请移步配置篇。 +使用主题的方法呢,就是在 `.vim` 文件夹下建立 `colors` 文件夹,再将主题文件放入。其后缀名为 `.vim` ,如: `onedark.vim` 。具体配置中怎么写,请移步配置篇。 #### 启动界面 -这个其实可有可无,是一个能快捷键打开历史记录的一个插件 vimplus-startify ,具体可以自己尝试。(至少没有乌干达儿童了,小声) +这个其实可有可无,是一个能快捷键打开历史记录的一个插件 vimplus-startify,具体可以自己尝试。(至少没有乌干达儿童了,小声) #### 小方便性插件 -commentary :快捷键 `gc` 注释选中行,`gcu` 撤销上次注释。 +commentary:快捷键 `gc` 注释选中行, `gcu` 撤销上次注释。 -ale :`:w` 保存时提示语法错误,并且可以开启与 `airline` 的携同,状态栏上也会显示 `Error` 和 `Warning`。 +ale: `:w` 保存时提示语法错误,并且可以开启与 `airline` 的携同,状态栏上也会显示 `Error` 和 `Warning` 。 -easymotion :快速跳转,我自己其实都不会用 233,需要可以查阅资料。 +easymotion:快速跳转,我自己其实都不会用 233,需要可以查阅资料。 -rainbow : 彩虹括号,使具有包含关系的括号显现出不同的颜色,增强多括号代码的可读性。具体还需要一些东西,Please 上作者的 github 项目观看。 +rainbow:彩虹括号,使具有包含关系的括号显现出不同的颜色,增强多括号代码的可读性。具体还需要一些东西,Please 上作者的 github 项目观看。 -delimitMate : 括号补全功能。同时考试中可用配置实现部分功能,配置篇中会讲述。 +delimitMate:括号补全功能。同时考试中可用配置实现部分功能,配置篇中会讲述。 -vimcdoc :汉化 Vim 在线文档。 +vimcdoc:汉化 Vim 在线文档。 -gundo :这个插件将能够显示你的文件修改树,就像 github 上一般能够回到历史版本,时光机啊 QwQ 。Vim 中`:GundoToggle`即可在左侧打开时光机。但是需要 Vim 开启 python 支持,请自行百度。 +gundo:这个插件将能够显示你的文件修改树,就像 github 上一般能够回到历史版本,时光机啊 QwQ。Vim 中 `:GundoToggle` 即可在左侧打开时光机。但是需要 Vim 开启 python 支持,请自行百度。 -vimim :这个的安装不在配置中 (`* 那是之前 emm,现在 kuai 到了我自己的 github 上,可以直接拉了 *`),相当于 Vim 自带中文输入法,需在 `.vim` 中创建文件夹 plugin 并把 [从这里](https://www.vim.org/scripts/download_script.php?src_id=23122) 下得的文件扔入此文件夹中即可。打开 Vim 并进入插入模式,按下 Ctrl + / 即可启用。但是使用的是云词库,若没网就会卡死。所以建议下载[本地超大词库](https://github.com/vimim/vimim/raw/master/plugin/vimim.gbk.bsddb),也放入 plugin 文件夹中,与插件脚本同目录即可启用。 +vimim:这个的安装不在配置中 ( `* 那是之前 emm,现在 kuai 到了我自己的 github 上,可以直接拉了 *` ),相当于 Vim 自带中文输入法,需在 `.vim` 中创建文件夹 plugin 并把[从这里](https://www.vim.org/scripts/download_script.php?src_id=23122)下得的文件扔入此文件夹中即可。打开 Vim 并进入插入模式,按下Ctrl+/即可启用。但是使用的是云词库,若没网就会卡死。所以建议下载[本地超大词库](https://github.com/vimim/vimim/raw/master/plugin/vimim.gbk.bsddb),也放入 plugin 文件夹中,与插件脚本同目录即可启用。 -vim-instant-markdown :这个插件可就厉害了。Vim 用习惯了之后什么都想用 Vim 来做,比如想用 Vim 来写 Markdown 并实时预览怎么办?于是这个强大的插件就诞生了,当打开 Markdown 文件时会自动在浏览器中打开一个标签页,将能够实时预览你的 Vim 中的 markdown 内容。还有额外的需要,请至 github 首页了解详情。 +vim-instant-markdown:这个插件可就厉害了。Vim 用习惯了之后什么都想用 Vim 来做,比如想用 Vim 来写 Markdown 并实时预览怎么办?于是这个强大的插件就诞生了,当打开 Markdown 文件时会自动在浏览器中打开一个标签页,将能够实时预览你的 Vim 中的 markdown 内容。还有额外的需要,请至 github 首页了解详情。 一切插件的安装写法及快捷键及配置皆在配置篇中,请移步。 ### 配置篇 -[我的配置](https://github.com/LuoshuiTianyi/Vim-for-OIWiki) Ps: 我的 .vimrc 时刻在改,所以这只是个副本…… +[我的配置](https://github.com/LuoshuiTianyi/Vim-for-OIWiki)Ps: 我的 .vimrc 时刻在改,所以这只是个副本…… -结合我的配置讲一讲一些 Vim 中的小细节和快捷键以及一些…… 七里八里的东西? +结合我的配置讲一讲一些 Vim 中的小细节和快捷键以及一些……七里八里的东西? Vim 的配置语法没那么麻烦,基本上就是 set 开启选项,call xxx() 调用函数,func 与 endfunc 定义函数,exec 执行命令,if 和 endif 描述以下条件表达式," 表示注释,source 表示应用啥的,语法和 Vim 命令行下一模一样,只是当你把配置文件写入,Vim 开启时会自动执行配置中的每一行语句。 #### 基础设置 -我必须说我的配置里其实没有背景方面的设置,因为我黑背景加个透明化很舒服了…… 接下来我会挑重要的配置来讲,剩下的可以结合我的配置内的注释来看 +我必须说我的配置里其实没有背景方面的设置,因为我黑背景加个透明化很舒服了……接下来我会挑重要的配置来讲,剩下的可以结合我的配置内的注释来看 首先使用各种插件容易与 vi 的模式产生冲突,所以我们要关闭 vi 的功能,那么就有了如下配置: @@ -438,7 +438,7 @@ syntax on 分别是开启高亮支持与开启语法高亮 -然后是我们可爱的状态栏,`set laststatus=2` 这行配置将会使得状态栏总是显示,而状态栏所显示的信息在配置中是可以设置的。设置如下: +然后是我们可爱的状态栏, `set laststatus=2` 这行配置将会使得状态栏总是显示,而状态栏所显示的信息在配置中是可以设置的。设置如下: ```vim set statusline=\ %<%F[%1*%M%*%n%R%H]%=\ %y\ %0(%{&fileformat}\ [%{(&fenc==\"\"?&enc:&fenc).(&bomb?\",BOM\":\"\")}]\ %c:%l/%L%) @@ -448,26 +448,26 @@ set statusline=\ %<%F[%1*%M%*%n%R%H]%=\ %y\ %0(%{&fileformat}\ [%{(&fenc==\"\"?& 再然后,默认情况下换行符是不可被删除的,除非使用 `dd` 命令或者 `J` 命令才可做到。那么我们需要 `set backspace=indent,eol,start` 这行配置来解除这种限制。 -显然还有一件事,那就是行号的问题。不管是评测文件写了多少行还是想要使用 `数字 + G` 的命令跳至指定行,没有行号的显示肯定是崩溃的。那么可以使用 `set number` 开启行号显示的功能。然后是 Vim 的自动折行功能,那就是当某一行超过了 Vim 窗口的边界,Vim 会怎么做呢?多出的部分会自动显示在下一行,而这种多出来的行前面是没有行号的,比较好辨认,这些行被称为屏幕行,而根据行号一一对应的便称作实际行。但是仅仅凭着看前面的行号来辨认某个折下来的行属于那个实际行的话,还是不够快。我们可以使用`set cursorline`来开启高亮显示当前行,而这个高亮也是可以设置的,我的配置里也有。 +显然还有一件事,那就是行号的问题。不管是评测文件写了多少行还是想要使用 `数字 + G` 的命令跳至指定行,没有行号的显示肯定是崩溃的。那么可以使用 `set number` 开启行号显示的功能。然后是 Vim 的自动折行功能,那就是当某一行超过了 Vim 窗口的边界,Vim 会怎么做呢?多出的部分会自动显示在下一行,而这种多出来的行前面是没有行号的,比较好辨认,这些行被称为屏幕行,而根据行号一一对应的便称作实际行。但是仅仅凭着看前面的行号来辨认某个折下来的行属于那个实际行的话,还是不够快。我们可以使用 `set cursorline` 来开启高亮显示当前行,而这个高亮也是可以设置的,我的配置里也有。 用 Vim 的一个烦恼事就是临时文件 `swap` 啥的 ...... 太烦了。以下两行配置能够禁止其生成 set nobackup " 设置不备份 set noswapfile " 禁止生成临时文件 -至于主题...... 只需要如下一行 +至于主题……只需要如下一行 ```vim colorscheme xxx ``` -xxx 就是你的主题名称 (去掉 .vim 后缀名) +xxx 就是你的主题名称(去掉 .vim 后缀名) -然后是我们在基础篇中提到过的,开启鼠标支持`set mouse=a`,以及插件篇中提及的`set autochdir`与进阶篇中有的`set fillchars=vert:\ ,stl:\ ,stlnc:\` 这三个配置,作用各有提及。 +然后是我们在基础篇中提到过的,开启鼠标支持 `set mouse=a` ,以及插件篇中提及的 `set autochdir` 与进阶篇中有的 `set fillchars=vert:\ ,stl:\ ,stlnc:\` 这三个配置,作用各有提及。 其他的往我配置里看啦 wwww。 -那个 `zsh` 是一个 shell 的相关程序,有兴趣的可以查查,特点是补全强大。`Tab` 补全近乎完美,因为它对于文件名的补全远强于 shell 终端。 +那个 `zsh` 是一个 shell 的相关程序,有兴趣的可以查查,特点是补全强大。 `Tab` 补全近乎完美,因为它对于文件名的补全远强于 shell 终端。 还有一件事,就是文件编码,设置如下: @@ -481,13 +481,13 @@ set fileencodings=utf8,ucs-bom,gbk,cp936,gb2312,gb18030 #### 快捷键设置 -其实 Vim 普通模式下没有多少按键是 "自由身",那么用户该如何定制自己的快捷键呢?Vim 为此提供了 leader 键来服务。leader 键在配置中由自己定制,只需要短短一行 +其实 Vim 普通模式下没有多少按键是 "自由身",那么用户该如何定制自己的快捷键呢?Vim 为此提供了leader键来服务。leader键在配置中由自己定制,只需要短短一行 ```vim let mapleader = "" ``` -双引号之间就是你自己定义的 leader 键啦。 +双引号之间就是你自己定义的leader键啦。 设置快捷键怎么写呢? @@ -498,15 +498,15 @@ inoremap 快捷键 指令 两行分别代表了在普通模式下和插入模式下的快捷键执行指令。当然指令不用想多了,没有什么语法,就是相当于在键盘上按你指令中写下的键而已…… -首先我的个人快捷键需求其实不是很多,我的 leader 键是 \`,但是处于一种坐冷板凳的状态,就更新插件的时候用一用,不过还是很方便的,我的设置是: +首先我的个人快捷键需求其实不是很多,我的leader键是\`,但是处于一种坐冷板凳的状态,就更新插件的时候用一用,不过还是很方便的,我的设置是: ```vim nnoremap i :PluginInstall ``` -``代表回车。设置之后只需要连续按 ``i 即可更新插件,很方便。 + `` 代表回车。设置之后只需要连续按 `` i即可更新插件,很方便。 -那么你有没有猜到如何利用配置写出括号补全的部分功能呢?没错,就是利用快捷键。将插入模式下的左扩号当做快捷键即可,指令就是`()`。如果补全后要使光标在括号里怎么办呢?如果仔细观察你就会发现每当退出插入模式,光标总是会向前跳一个字符,我们可以利用这一点,组合 `Esc + i` 不就变成了向前一个字符进行插入吗?总结下来配置如下: +那么你有没有猜到如何利用配置写出括号补全的部分功能呢?没错,就是利用快捷键。将插入模式下的左扩号当做快捷键即可,指令就是 `()` 。如果补全后要使光标在括号里怎么办呢?如果仔细观察你就会发现每当退出插入模式,光标总是会向前跳一个字符,我们可以利用这一点,组合 `Esc + i` 不就变成了向前一个字符进行插入吗?总结下来配置如下: ```vim inoremap ( ()i @@ -526,9 +526,9 @@ nnoremap j nnoremap k ``` -应该比原来的按法好记也好按…… 吧…… +应该比原来的按法好记也好按……吧…… -还记得自动折行吧,我们的`hjkl`命令其实都是在实际行之间移动,而折下来的屏幕行实在是没法子,只能用 `l` 键不断移过去。但实际上,`g + 移动命令` 便能够使你在屏幕行间移动,因为考虑到这种移动的常用,我选择将`g + 移动命令`与移动命令反过来映射。 +还记得自动折行吧,我们的 `hjkl` 命令其实都是在实际行之间移动,而折下来的屏幕行实在是没法子,只能用 `l` 键不断移过去。但实际上, `g + 移动命令` 便能够使你在屏幕行间移动,因为考虑到这种移动的常用,我选择将 `g + 移动命令` 与移动命令反过来映射。 ```vim noremap j gj @@ -537,7 +537,7 @@ noremap gk k noremap k gk ``` -刚才也都说了,自由身的快捷键不多,`F1~F12` 就是方便而自由的快捷键。那用它们来干嘛呢? +刚才也都说了,自由身的快捷键不多, `F1~F12` 就是方便而自由的快捷键。那用它们来干嘛呢? F9一键编译 @@ -560,16 +560,15 @@ func! CompileRunGcc() endfunc ``` -第一行代表运行 `CompileRunGcc` 函数,第二行代表定义函数,三至五行代表函数运行内容,第六行代表函数结束。`exec` 表示执行命令,`%` 表示当前文件名,`%<` 表示当前文件名去掉后缀的名字。我想你应该是看得懂函数内容的。 -`time` 选项则是回显程序运行时间。 +第一行代表运行 `CompileRunGcc` 函数,第二行代表定义函数,三至五行代表函数运行内容,第六行代表函数结束。 `exec` 表示执行命令, `%` 表示当前文件名, `%<` 表示当前文件名去掉后缀的名字。我想你应该是看得懂函数内容的。 `time` 选项则是回显程序运行时间。 -不过如果你使用得多了,就会发现当按下 F9 的时候转到另一个屏即终端进行运行,但是每运行一次都会多一些信息。如此累积的话多来几次整个终端就满了,这时可以使用 shell 下的命令 +不过如果你使用得多了,就会发现当按下F9的时候转到另一个屏即终端进行运行,但是每运行一次都会多一些信息。如此累积的话多来几次整个终端就满了,这时可以使用 shell 下的命令 ```shell clear ``` -来清屏,不过我倾向于也把它封装在一个快捷键内,按 F12 就会自动清屏了,个人觉得用着挺爽…… +来清屏,不过我倾向于也把它封装在一个快捷键内,按F12就会自动清屏了,个人觉得用着挺爽…… ```vim nnoremap :call Clss() @@ -578,7 +577,7 @@ func! Clss() endfunc ``` -还有,在 Vim 中执行外部命令纵使有 `:!` 的方法,其实还是不方便,要是能直接在 Vim 中再打开一个终端就好了,对吧。Vim 从 8.0 之后就增添了在内部分个屏来打开一个终端的功能,命令是 `:terminal`。我个人也将它设置成了快捷键,作为强迫症还是装在了函数中 = =。我想有了命令你应该自己会写了。 +还有,在 Vim 中执行外部命令纵使有 `:!` 的方法,其实还是不方便,要是能直接在 Vim 中再打开一个终端就好了,对吧。Vim 从 8.0 之后就增添了在内部分个屏来打开一个终端的功能,命令是 `:terminal` 。我个人也将它设置成了快捷键,作为强迫症还是装在了函数中 = =。我想有了命令你应该自己会写了。 ```vim nnoremap :call Term() @@ -589,7 +588,7 @@ func! Term() 按F8就能在上面分出一个窗口打开终端了。 -介于更各种 Vim 版本的压迫,Vim 作者也是奋发图强,Vim 8.1 又更新了调试程序,先用`packadd termdebug`开启此设置,然后在 Vim 中输入`:Termdebug + 编译出的程序名称`即可开始 GDB 的过程,具体详细操作可以参考[这篇文章](https://fzheng.me/2018/05/28/termdebug/)。这个自然也被我封装函数了 >\_<。 +介于更各种 Vim 版本的压迫,Vim 作者也是奋发图强,Vim 8.1 又更新了调试程序,先用 `packadd termdebug` 开启此设置,然后在 Vim 中输入 `:Termdebug + 编译出的程序名称` 即可开始 GDB 的过程,具体详细操作可以参考[这篇文章](https://fzheng.me/2018/05/28/termdebug/)。这个自然也被我封装函数了 >\_<。 ```vim packadd termdebug @@ -601,13 +600,13 @@ func! GDB() #### 写代码好用的 -首先是 Tab 键,我们可以用 `set tabstop=` 来定义 Tab 的长度,一般当然是 4 个空格,在等于号后面填的数字是多少那么长度就是多少空格。 +首先是Tab键,我们可以用 `set tabstop=` 来定义Tab的长度,一般当然是 4 个空格,在等于号后面填的数字是多少那么长度就是多少空格。 然后是写代码的时候,当多个括号嵌套时用肉眼显然不好看出对应的括号,那么我们可以用 `set showmatch` 开启高亮显示匹配括号。 有的时候打开 Vim 是不是经常会提示有什么 swap 文件是否确认啥的,那个是临时缓存文件,挺烦的,我们可以使用 `set nobackup` 与 `set noswapfilei` 来禁止其生成,这样就方便舒爽多了(还是开着吧)。 -最后嘛,大多数时候调试代码都会用 `freopen` 来输入输出,再利用分屏操作来打开 `.in` `.out` 文件,就可以实时看到结果。不过每次运行程序之后你都会发现因为 `.out` 文件的修改而会弹出一个确认选项是否重新加载文件,这个也是很不爽的,我们可以开启 `set autoread` 选项以自动加载改动的文件。 +最后嘛,大多数时候调试代码都会用 `freopen` 来输入输出,再利用分屏操作来打开 `.in` `.out` 文件,就可以实时看到结果。不过每次运行程序之后你都会发现因为 `.out` 文件的修改而会弹出一个确认选项是否重新加载文件,这个也是很不爽的,我们可以开启 `set autoread` 选项以自动加载改动的文件。 #### 关于插件 @@ -652,7 +651,7 @@ Plugin 'suan/vim-instant-markdown' " markdown 实时预览 call vundle#end() ``` -关于插件其实也有相关配置,但是都写在一起将会使得 `.vimrc` 十分臃肿,我们可以额外写在别的文件里,一般文件应该保存在 `home` 下,然后在配置中写下 `source $HOME / 文件路径` 即可。我的 nerdtree, syntastic 和 airline 都额外写了别的文件。 +关于插件其实也有相关配置,但是都写在一起将会使得 `.vimrc` 十分臃肿,我们可以额外写在别的文件里,一般文件应该保存在 `home` 下,然后在配置中写下 `source $HOME/文件路径` 即可。我的 nerdtree, syntastic 和 airline 都额外写了别的文件。 同时我的配置里关于插件的快捷键如下: @@ -669,7 +668,7 @@ F7 :启动 Gundo 时光机 F10 :启动 nerdtree 侧边工程目录树 F7 :启动 Gundo 时光机 -## Emacs —— 神的编辑器 +## Emacs——神的编辑器 15 分钟入门 Emacs(因为是入门教程,所以比较简短) @@ -772,10 +771,10 @@ Emacs 是一款非常容易上手的编辑器,重要的快捷键不多,随 ### 简介 -Visual Studio Code (以下简称 VS Code) 是一个免费、开源、跨平台的由微软开发的程序编辑器。它是用 Typescript 编写的,并且采用 Electron 架构。官网是 。它带有对 JavaScript、TypeScript 和 Node.js 的内置支持,并为其他语言(如 C++、Cype、Java、Python、PHP、GO)提供了丰富的扩展生态系统。 +Visual Studio Code(以下简称 VS Code) 是一个免费、开源、跨平台的由微软开发的程序编辑器。它是用 Typescript 编写的,并且采用 Electron 架构。官网是。它带有对 JavaScript、TypeScript 和 Node.js 的内置支持,并为其他语言(如 C++、Cype、Java、Python、PHP、GO)提供了丰富的扩展生态系统。 ## Atom - GitHub 家的编辑器 ### 简介 -Atom 是一个免费、开源、跨平台的由 GitHub 开发的程序编辑器。它是用 JavaScript 编写的,并且采用 Electron 架构。官网是 。它的一个较大缺点就是性能差。 +Atom 是一个免费、开源、跨平台的由 GitHub 开发的程序编辑器。它是用 JavaScript 编写的,并且采用 Electron 架构。官网是。它的一个较大缺点就是性能差。 diff --git a/docs/intro/faq.md b/docs/intro/faq.md index f6a45b33..6be7024a 100644 --- a/docs/intro/faq.md +++ b/docs/intro/faq.md @@ -1,40 +1,40 @@ ## 交流方式 -本项目主要使用 [Issues](https://github.com/24OI/OI-wiki/issues) / [QQ](https://jq.qq.com/?_wv=1027&k=5EfkM6K) / [Telegram](https://t.me/OIwiki) 进行交流沟通。 +本项目主要使用[Issues](https://github.com/24OI/OI-wiki/issues)/[QQ](https://jq.qq.com/?_wv=1027&k=5EfkM6K)/[Telegram](https://t.me/OIwiki)进行交流沟通。 -Telegram 群组链接为 [@OIwiki](https://t.me/OIwiki) , QQ 群号码为 [`588793226`](https://jq.qq.com/?_wv=1027&k=5EfkM6K),欢迎加入。 +Telegram 群组链接为[@OIwiki](https://t.me/OIwiki),QQ 群号码为[ `588793226` ](https://jq.qq.com/?_wv=1027&k=5EfkM6K),欢迎加入。 ## 贡献方式 -**我们现在在使用 [Projects](https://github.com/24OI/OI-wiki/projects),这里详细列举了正在做的事情以及待做事项。** +**我们现在在使用[Projects](https://github.com/24OI/OI-wiki/projects),这里详细列举了正在做的事情以及待做事项。** -**在开始编写一段内容之前,请查阅 [Issues](https://github.com/24OI/OI-wiki/issues),确认没有别人在做相同的工作之后,** +**在开始编写一段内容之前,请查阅[Issues](https://github.com/24OI/OI-wiki/issues),确认没有别人在做相同的工作之后,** -**开个 [新 issue](https://github.com/24OI/OI-wiki/issues/new) 记录你要编写的内容。** +**开个[新 issue](https://github.com/24OI/OI-wiki/issues/new)记录你要编写的内容。** ### 我之前没怎么用过 GitHub -参与 Wiki 的编写 **需要** 一个 GitHub 账号, **不需要** 高超的 GitHub 技巧。 +参与 Wiki 的编写**需要**一个 GitHub 账号,**不需要**高超的 GitHub 技巧。 举个栗子,假如我想要修改一个页面内容,应该怎么操作呢? 1. 在 OI Wiki 网站上找到对应页面。 -2. 点击 正文右上方、目录左侧的 **“编辑此页”** edit 按钮。 -3. (应该已经跳转到了 GitHub 上的对应页面吧?)这时候右上方还会有一个 **“编辑此页”** edit 的按钮,点击它就可以在线编辑了。 +2. 点击 正文右上方、目录左侧的**“编辑此页”**edit按钮。 +3. (应该已经跳转到了 GitHub 上的对应页面吧?)这时候右上方还会有一个**“编辑此页”**edit的按钮,点击它就可以在线编辑了。 4. 写好了之后点下方的绿色按钮(Propose file change),可能会提示没有权限。不必担心!GitHub 会自动帮你 fork 一份项目的文件并创建 Pull Request。 5. 之后点上方的绿色按钮(Create pull request)后,再点一下出现的绿色按钮(Create pull request)。 -6. 提交之后就可以等待他人合并或者指出还要修改的地方,当然你也可以给他人的 PR 提出修改意见,或者只是点赞 / 踩。如果有消息,会有邮件通知和 / 或网页上的提醒(取决于在你个人 Settings 中的设置)。 +6. 提交之后就可以等待他人合并或者指出还要修改的地方,当然你也可以给他人的 PR 提出修改意见,或者只是点赞/踩。如果有消息,会有邮件通知和/或网页上的提醒(取决于在你个人 Settings 中的设置)。 (有木有很简单?) -如果还是不放心,可以参考[这篇文章](https://juejin.im/entry/56e638591ea49300550885cc),或者试试 [Github 的官方教程](https://lab.github.com/)。 +如果还是不放心,可以参考[这篇文章](https://juejin.im/entry/56e638591ea49300550885cc),或者试试[Github 的官方教程](https://lab.github.com/)。 ### 我之前用过 GitHub 基本协作方式如下: 1. Fork 主仓库到自己的仓库中。 -2. 当想要贡献某部分内容时,请务必仔细查看 **Issues**,以便确定是否有人已经开始了这项工作。当然,我们更希望你可以加入 QQ / Telegram 群组,方便交流。 +2. 当想要贡献某部分内容时,请务必仔细查看**Issues**,以便确定是否有人已经开始了这项工作。当然,我们更希望你可以加入 QQ/Telegram 群组,方便交流。 3. 在决定将内容推送到本仓库时,**请你首先拉取本仓库代码进行合并,自行处理好冲突,同时确保在本地可以正常生成文档**,然后再将分支 PR 到主仓库的 master 分支上。其中,PR 需要包含以下基本信息: 标题:本次 PR 的目的(做了什么工作,修复了什么问题); 内容:如果必要的话,请给出对修复问题的叙述。 @@ -49,56 +49,56 @@ Telegram 群组链接为 [@OIwiki](https://t.me/OIwiki) , QQ 群号码为 [`58 ### 文档内容的基本格式 -在提交 PR 前,请先确保文档内容符合 [如何贡献 How to contribute](https://github.com/24OI/OI-wiki/wiki/%E5%A6%82%E4%BD%95%E8%B4%A1%E7%8C%AE---How-to-contribute) 中的格式要求。格式缺乏基本的规范性、严谨性可能会使你的贡献不能及时通过审核。 +在提交 PR 前,请先确保文档内容符合[如何贡献 How to contribute](https://github.com/24OI/OI-wiki/wiki/%E5%A6%82%E4%BD%95%E8%B4%A1%E7%8C%AE---How-to-contribute)中的格式要求。格式缺乏基本的规范性、严谨性可能会使你的贡献不能及时通过审核。 -文档内容的基本格式主要是指 [中文排版指南](https://github.com/ctf-wiki/ctf-wiki/wiki/%E4%B8%AD%E6%96%87%E6%8E%92%E7%89%88%E6%8C%87%E5%8D%97) 与 [MkDocs 使用说明](https://github.com/ctf-wiki/ctf-wiki/wiki/Mkdocs-%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E)。后者额外介绍了 mkdocs-material 主题的插件使用方式。 +文档内容的基本格式主要是指[中文排版指南](https://github.com/ctf-wiki/ctf-wiki/wiki/%E4%B8%AD%E6%96%87%E6%8E%92%E7%89%88%E6%8C%87%E5%8D%97)与[MkDocs 使用说明](https://github.com/ctf-wiki/ctf-wiki/wiki/Mkdocs-%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E)。后者额外介绍了 mkdocs-material 主题的插件使用方式。 -如果对 mkdocs-material (我们使用的这个主题)还有什么问题,还可以查阅 [cyent 的笔记](https://cyent.github.io/markdown-with-mkdocs-material/),他有介绍 markdown 传统语法和 mkdocs-material 支持的扩展语法。 +如果对 mkdocs-material(我们使用的这个主题)还有什么问题,还可以查阅[cyent 的笔记](https://cyent.github.io/markdown-with-mkdocs-material/),他有介绍 markdown 传统语法和 mkdocs-material 支持的扩展语法。 ### 文档的合理性 -所谓合理性,指所编写的 **内容** 必须具有如下的特性: +所谓合理性,指所编写的**内容**必须具有如下的特性: - 由浅入深,内容的难度应该具有渐进性。 - 逻辑性,对于每类内容的撰写应该尽量包含以下的内容: - 原理,说明该内容对应的原理。 - 例子,给出 1 ~ 2 个典型的例子。 - - 题目,在该标题下, **只需要给出题目名字、题目链接**。 + - 题目,在该标题下,**只需要给出题目名字、题目链接**。 ### 文档存储的格式 对于每类要编写的内容,对应的文档应该存储在合适的目录下。 -- images, 存储文档介绍时所使用的图片,位置为所添加的目录下(即格式为 `![](./images/xx.jpg)`)。 -- **文件名请务必都小写,以 `-` 分割, 如 `file-name`。** +- images,存储文档介绍时所使用的图片,位置为所添加的目录下(即格式为 `![](./images/xx.jpg)` )。 +- **文件名请务必都小写,以 `-` 分割,如 `file-name` 。** ## F.A.Q. ### 目录在哪 -目录在项目根目录下的 [mkdocs.yml](https://github.com/24OI/OI-wiki/blob/master/mkdocs.yml#L17) 文件中。 +目录在项目根目录下的[mkdocs.yml](https://github.com/24OI/OI-wiki/blob/master/mkdocs.yml#L17)文件中。 ### 如何修改一个 topic 的内容 -在对应页面右上方有一个编辑按钮 edit,点击之后会跳转到 GitHub 上对应文件的位置。 +在对应页面右上方有一个编辑按钮edit,点击之后会跳转到 GitHub 上对应文件的位置。 -或者也可以自行阅读目录 [(mkdocs.yml)](https://github.com/24OI/OI-wiki/blob/master/mkdocs.yml#L17) 查找文件位置。 +或者也可以自行阅读目录[(mkdocs.yml)](https://github.com/24OI/OI-wiki/blob/master/mkdocs.yml#L17)查找文件位置。 ### 如何添加一个 topic 1. 可以开一个 Issue,注明希望能添加的内容。 -2. 可以开一个 Pull Request,在目录 [(mkdocs.yml)](https://github.com/24OI/OI-wiki/blob/master/mkdocs.yml#L17) 中加上新的 topic,并在 [docs](https://github.com/24OI/OI-wiki/tree/master/docs) 文件夹下对应位置创建一个空的 `.md` 文件。 +2. 可以开一个 Pull Request,在目录[(mkdocs.yml)](https://github.com/24OI/OI-wiki/blob/master/mkdocs.yml#L17)中加上新的 topic,并在[docs](https://github.com/24OI/OI-wiki/tree/master/docs)文件夹下对应位置创建一个空的 `.md` 文件。 !!! warning "注意" 写 .md 文件时,请勿在开头写上标题。 ### commit message 怎么写 -我们推荐使用 [commitizen/cz-cli](https://github.com/commitizen/cz-cli) 来规范 commit message (并非强求)。 +我们推荐使用[commitizen/cz-cli](https://github.com/commitizen/cz-cli)来规范 commit message(并非强求)。 ### 我尝试访问 GitHub 的时候遇到了困难 -推荐在 hosts 文件中加入如下几行:(来源: [@GoogleHosts](https://github.com/googlehosts/hosts/blob/master/hosts-files/hosts#L481-L485)) +推荐在 hosts 文件中加入如下几行:(来源:[@GoogleHosts](https://github.com/googlehosts/hosts/blob/master/hosts-files/hosts#L481-L485)) ```text ## GitHub Start @@ -108,7 +108,7 @@ Telegram 群组链接为 [@OIwiki](https://t.me/OIwiki) , QQ 群号码为 [`58 ## GitHub End ``` -可以在 [@GoogleHosts 主页](https://github.com/googlehosts/hosts) 上了解到更多信息。 +可以在[@GoogleHosts 主页](https://github.com/googlehosts/hosts)上了解到更多信息。 ### 我这里 pip 也太慢了 @@ -120,7 +120,7 @@ pip install -U -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple/ ### 我在客户端 clone 了这个项目,速度太慢 -如果有安装 `git bash`,可以加几个限制来减少下载量: +如果有安装 `git bash` ,可以加几个限制来减少下载量: ```bash git clone https://github.com/24OI/OI-wiki.git --depth=1 -b master @@ -130,11 +130,11 @@ git clone https://github.com/24OI/OI-wiki.git --depth=1 -b master ### 我没装过 Python 3 -可以访问 [Python 官网](https://www.python.org/downloads/) 了解更多信息。 +可以访问[Python 官网](https://www.python.org/downloads/)了解更多信息。 ### 好像提示我 pip 版本过低 -进入 cmd / shell 之后, +进入 cmd/shell 之后, ```bash python -m pip install --upgrade pip @@ -160,22 +160,22 @@ pip install -U -r requirements.txt ### 为什么我的 markdown 格式乱了 -可以查阅 [cyent 的笔记](https://cyent.github.io/markdown-with-mkdocs-material/),或者 [MkDocs 使用说明](https://github.com/ctf-wiki/ctf-wiki/wiki/Mkdocs-%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E)。 +可以查阅[cyent 的笔记](https://cyent.github.io/markdown-with-mkdocs-material/),或者[MkDocs 使用说明](https://github.com/ctf-wiki/ctf-wiki/wiki/Mkdocs-%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E)。 -我们目前在使用 [remark-lint](https://github.com/remarkjs/remark-lint) 来自动化修正格式,可能还有一些 [配置](https://github.com/24OI/OI-wiki/blob/master/.remarkrc) 不够好的地方,欢迎指出。 +我们目前在使用[remark-lint](https://github.com/remarkjs/remark-lint)来自动化修正格式,可能还有一些[配置](https://github.com/24OI/OI-wiki/blob/master/.remarkrc)不够好的地方,欢迎指出。 #### remark-lint 要求怎样的格式 -我们现在启用的配置文件在 [.remarkrc](https://github.com/24OI/OI-wiki/blob/master/.remarkrc),它可以自动给项目内文件统一风格。 +我们现在启用的配置文件在[.remarkrc](https://github.com/24OI/OI-wiki/blob/master/.remarkrc),它可以自动给项目内文件统一风格。 在配置过程中我们也遇到了一些 remark-lint 不能很好处理的问题: -1. `## 简介` 标题要空一格(英文半角空格),也不要写成 `## 简介 ##`。 +1. `## 简介` 标题要空一格(英文半角空格),也不要写成 `## 简介 ##` 。 2. 列表 1. 列表前要有空行,新开一段。 - 2. `1. 例子` 点号后要有空格。 + 2. `1. 例子` 点号后要有空格。 3. 行间公式不能写在一行里,否则会被当做是行内公式 -4. 伪代码请使用 ```` ```text```` +4. 伪代码请使用 ```` ```text```` #### GitHub 是不是不显示我的数学公式? @@ -183,7 +183,7 @@ pip install -U -r requirements.txt #### 我的数学公式怎么乱码了 -如果是行间公式(用的 `$$`),目前已知的问题是需要在 `$$` 两侧留有空行,且 `$$` 要 **单独** 放在一行里(且不要在前加空格)。格式如下: +如果是行间公式(用的 `$$` ),目前已知的问题是需要在 `$$` 两侧留有空行,且 `$$` 要**单独**放在一行里(且不要在前加空格)。格式如下: ```text // 空行 @@ -197,7 +197,7 @@ $$ 是的,这个是 python-markdown 的一个 bug,可能近期会修复。 -如果现在想要避免目录中出现双倍公式,可以参考 +如果现在想要避免目录中出现双倍公式,可以参考 ```text ### 结束位置 @@ -213,7 +213,7 @@ $$ ### 如何给一个页面单独声明版权信息 -参考 [Metadata](https://squidfunk.github.io/mkdocs-material/extensions/metadata/#usage) 的使用,在页面开头加一行即可。 +参考[Metadata](https://squidfunk.github.io/mkdocs-material/extensions/metadata/#usage)的使用,在页面开头加一行即可。 比如: @@ -221,11 +221,11 @@ $$ copyright: SATA ``` -注:默认的是 ‘CC BY-SA 4.0 和 SATA’。 +注:默认的是‘CC BY-SA 4.0 和 SATA’。 -### 如何给一个页面关闭字数统计 (现已默认关闭) +### 如何给一个页面关闭字数统计(现已默认关闭) -参考 [Metadata](https://squidfunk.github.io/mkdocs-material/extensions/metadata/#usage) 的使用,在页面开头加一行即可。 +参考[Metadata](https://squidfunk.github.io/mkdocs-material/extensions/metadata/#usage)的使用,在页面开头加一行即可。 比如: diff --git a/docs/intro/judgers.md b/docs/intro/judgers.md index e0ef414b..81c37124 100644 --- a/docs/intro/judgers.md +++ b/docs/intro/judgers.md @@ -2,7 +2,7 @@ ## Cena -Cena 是由刘其帅和李子星使用 Pascal 语言编写的开源评测工具,是流传最广泛的本地评测工具。Cena 最初开源于 Google Code 平台,由于不明原因 Google 删除了 Cena 项目,目前可以在 [Web Archive](https://web.archive.org/web/20131023112258/http://code.google.com/p/cena/) 上找到 Cena 的官网。 +Cena 是由刘其帅和李子星使用 Pascal 语言编写的开源评测工具,是流传最广泛的本地评测工具。Cena 最初开源于 Google Code 平台,由于不明原因 Google 删除了 Cena 项目,目前可以在[Web Archive](https://web.archive.org/web/20131023112258/http://code.google.com/p/cena/)上找到 Cena 的官网。 Cena 的源代码可以在[这里](https://github.com/billchenchina/cena)找到。 @@ -12,11 +12,11 @@ Cena 对权限的限制不是很明确,测试的时候可以读测点 AC QAQ Lemon 是 zhipeng-jia 写的开源的评测工具,地址在:[zhipeng-jia/project-lemon](https://github.com/zhipeng-jia/project-lemon)。 -Ir1d 提供了一份 linux 下编译好的版本在 [FreestyleOJ/Project_lemon](https://github.com/FreestyleOJ/Project_lemon/tree/Built)。 +Ir1d 提供了一份 linux 下编译好的版本在[FreestyleOJ/Project_lemon](https://github.com/FreestyleOJ/Project_lemon/tree/Built)。 -Menci 提供了一份更新的版本在 [Menci/Lemon](https://github.com/Menci/Lemon/)。 +Menci 提供了一份更新的版本在[Menci/Lemon](https://github.com/Menci/Lemon/)。 -**注意** macOS 下 Lemon 可能会出现内存测试不准确的情况, 这是由于 mac 下没有一些 Linux 的监测工具,而 Lemon-Linux 也没有对于 macOS 的使用优化。 +**注意**macOS 下 Lemon 可能会出现内存测试不准确的情况,这是由于 mac 下没有一些 Linux 的监测工具,而 Lemon-Linux 也没有对于 macOS 的使用优化。 ### 自行编译 @@ -80,9 +80,9 @@ players/ ... ``` -其中,`` 指的是选手编号,形如 `<省份>-< 编号 >`,例如 HL-001,JL-125 等等,`` 指的是题目名称。 +其中, `` 指的是选手编号,形如 `<省份>-< 编号 >` ,例如 HL-001,JL-125 等等, `` 指的是题目名称。 -当然,在自测时可以使用字母,短线(即 `-`)和数字的组合作为选手编号。 +当然,在自测时可以使用字母,短线(即 `-` )和数字的组合作为选手编号。 准备好选手文件夹还不够,需要准备选手名单。名单格式如下: @@ -104,9 +104,9 @@ players/ .in .ans ``` -其中,`` 是数据编号,编号从 1 开始。 +其中, `` 是数据编号,编号从 1 开始。 -默认测试数据后缀名是 `.ans`,选手输出的后缀名是 `.out`,不能混淆。不用将每题的测试数据放置在各自文件夹里,只需要放在一起即可。 +默认测试数据后缀名是 `.ans` ,选手输出的后缀名是 `.out` ,不能混淆。不用将每题的测试数据放置在各自文件夹里,只需要放在一起即可。 这样就准备好了,现在开始测评文件夹的配置。 @@ -152,9 +152,9 @@ players/ 我们把已经建好的选手程序文件夹放在 `players/` 目录下,将所有测试数据(不放在文件夹里)放在 `evaldata` 中。 -`filter/` 文件夹放置了一些比较器及其源代码,写自定义比较器时可以参考,`result/` 文件夹存放选手的测评结果,`tmp/` 文件夹是测评时文件夹。 + `filter/` 文件夹放置了一些比较器及其源代码,写自定义比较器时可以参考, `result/` 文件夹存放选手的测评结果, `tmp/` 文件夹是测评时文件夹。 -配置好后,就是正式测评环节了。点开 “试题评测” 标签,然后会出现如下所示情况。 +配置好后,就是正式测评环节了。点开“试题评测”标签,然后会出现如下所示情况。 ![Pretest](./images/arbiter_pretest.png) @@ -164,7 +164,7 @@ players/ ![Test](./images/arbiter_test.png) -因为我取得编号是 `HL-001`,所以会自动识别出 “所属” 一栏。如果不是 NOIP 规范的编号是识别不出来的。 +因为我取得编号是 `HL-001` ,所以会自动识别出“所属”一栏。如果不是 NOIP 规范的编号是识别不出来的。 这个时候,要用**向上箭头**把测评第 0 场变为测评第 1 场,如果直接修改的话会识别失败。 @@ -174,7 +174,7 @@ players/ #### 自定义校验器的编写 -注意编译后自定义校验器名称为 `_e`,其中 `` 为题目名称,必须放在 `filter/` 文件夹下。在配置题目时选择自定义校验器,然后选择需要的自定义校验器。 +注意编译后自定义校验器名称为 `_e` ,其中 `` 为题目名称,必须放在 `filter/` 文件夹下。在配置题目时选择自定义校验器,然后选择需要的自定义校验器。 可以参考 `filter/` 下的源代码编写。 @@ -194,7 +194,7 @@ players/ #### 诶我怎么只能看见代码不能看见每个点得多少分 -测试点详细信息需要在 `result/` 文件夹下查看,文件夹下会有选手的结果文件夹,结果文件的后缀名为 `.result`,用纯文本方式查看即可。 +测试点详细信息需要在 `result/` 文件夹下查看,文件夹下会有选手的结果文件夹,结果文件的后缀名为 `.result` ,用纯文本方式查看即可。 ~~(我觉得这个设计很值得吐槽)~~ @@ -210,11 +210,11 @@ players/ 至少不能读取答案文件…… -`bits/stdc++.h` 测得可用。 + `bits/stdc++.h` 测得可用。 -`#pragma G++ optimize("O2")` 竟然可用。 + `#pragma G++ optimize("O2")` 竟然可用。 -`__attribute__((__optimize__("-O2")))` 竟然也可用。 + `__attribute__((__optimize__("-O2")))` 竟然也可用。 我可能用的是假 Arbiter…… @@ -228,4 +228,4 @@ players/ ## CCR-Plus -一款开源的界面好看的评测工具 GitHub 地址 :[sxyzccr/CCR-Plus](https://github.com/sxyzccr/CCR-Plus) +一款开源的界面好看的评测工具 GitHub 地址:[sxyzccr/CCR-Plus](https://github.com/sxyzccr/CCR-Plus) diff --git a/docs/intro/mode.md b/docs/intro/mode.md index 50d41789..2f792739 100644 --- a/docs/intro/mode.md +++ b/docs/intro/mode.md @@ -1,6 +1,6 @@ ## 赛事介绍 -OI 竞赛是一项全球的赛事,每年夏天会有世界级竞赛(IOI)举行,参赛选手大多都经过层层选拔。对于大部分选手而言,每年新赛季从 10 月的 NOIP (省级选拔赛)开始。 +OI 竞赛是一项全球的赛事,每年夏天会有世界级竞赛(IOI)举行,参赛选手大多都经过层层选拔。对于大部分选手而言,每年新赛季从 10 月的 NOIP(省级选拔赛)开始。 OI 竞赛中允许使用的语言包括 Pascal(NOI 将于 2020 年停止使用 Pascal,NOIP 将于 2022 年停止使用 Pascal),C 和 C++。其中 C++ 的版本不同考试有不同的规定。考试题目一般为算法或者数据结构相关的内容,题目形式包括传统题(最常见的规定输入和输出到文件的题目)和非传统题(提交答案题、交互题、补全代码题等等)。 @@ -8,7 +8,7 @@ OI 竞赛中允许使用的语言包括 Pascal(NOI 将于 2020 年停止使用 NOIP(National Olympiad in Informatics in Provinces)是全国青少年信息学奥林匹克联赛,顾名思义,是以省为单位排名评奖,对于大部分高校来说,获得省一等奖可以用于获得自主招生资格。 -NOIP 分为初赛和复赛两个阶段。初赛会考察一些计算机基础知识和算法基础(笔试),复赛是上机考试,时间上一般是 11 月的第二个周末。全国使用同一套试卷,但是评奖规则是按照省内情况由 CCF (中国计算机学会)统一指定,并于赛后在 [NOI 官方网站](http://www.noi.cn) 上公布。 +NOIP 分为初赛和复赛两个阶段。初赛会考察一些计算机基础知识和算法基础(笔试),复赛是上机考试,时间上一般是 11 月的第二个周末。全国使用同一套试卷,但是评奖规则是按照省内情况由 CCF(中国计算机学会)统一指定,并于赛后在[NOI 官方网站](http://www.noi.cn)上公布。 ### 省选 @@ -20,7 +20,7 @@ NOI(National Olympiad in Informatics)是全国信息学奥林匹克竞赛, ### WC -WC(Winter Camp)是全国青少年信息学奥林匹克竞赛冬令营,是每年冬天在当年 NOI 举办地进行的一项活动,内容包括若干天的培训和一天的考试。这项考试主要用于从国家集训队( 50 人)选拔国家候选队( 15 人)。 +WC(Winter Camp)是全国青少年信息学奥林匹克竞赛冬令营,是每年冬天在当年 NOI 举办地进行的一项活动,内容包括若干天的培训和一天的考试。这项考试主要用于从国家集训队(50 人)选拔国家候选队(15 人)。 ### APIO @@ -28,13 +28,13 @@ APIO(Asia-Pacific Informatics Olympiad)是亚太地区信息学奥林匹克 ### CTSC -CTSC(China Team Selection Competition)是中国队选拔赛。用来从国家候选队( 15 人)中选拔国家队( 6 人)准备参加当年夏天的 IOI 比赛,其中正式选手 4 人,替补选手 2 人。 +CTSC(China Team Selection Competition)是中国队选拔赛。用来从国家候选队(15 人)中选拔国家队(6 人)准备参加当年夏天的 IOI 比赛,其中正式选手 4 人,替补选手 2 人。 -注: APIO 和 CTSC 都是以省为单位报名,一般是按照 NOIP 成绩排序来确定谁会有机会参加 APIO 和 CTSC (二者一般时间上非常接近)。 +注:APIO 和 CTSC 都是以省为单位报名,一般是按照 NOIP 成绩排序来确定谁会有机会参加 APIO 和 CTSC(二者一般时间上非常接近)。 ### IOI -IOI(International Olympiad in Informatics)是国际信息学奥林匹克竞赛,每个国家有四人参赛,比赛一般会有直播。IOI 赛制中每个题目会有 subtask (子任务),每个子任务对应一定的分数。 +IOI(International Olympiad in Informatics)是国际信息学奥林匹克竞赛,每个国家有四人参赛,比赛一般会有直播。IOI 赛制中每个题目会有 subtask(子任务),每个子任务对应一定的分数。 ### 学科营 @@ -54,15 +54,15 @@ NOIP、NOI、省选都是 OI 赛制。 ### IOI 赛制 目前国内比赛也在逐渐向 IOI 赛制靠拢。 -IOI 赛制可以赛时任意提交,可以即时查看评测结果, APIO、IOI 都是 IOI 赛制。 +IOI 赛制可以赛时任意提交,可以即时查看评测结果,APIO、IOI 都是 IOI 赛制。 -### ACM / ICPC 赛制 +### ACM/ICPC 赛制 -在 ACM / ICPC 比赛中一般是三个人使用一台机器,每个题目只有在所有数据点全部正确后才能得到分数。比赛过程中可以有多次提交机会,实时评测并返回结果。比赛排名根据做题数和罚时来评判,罚时是通过题目的用时之和加上错误提交次数乘以一个系数。在 ACM 相关赛事中,选手允许带纸质资料。 +在 ACM/ICPC 比赛中一般是三个人使用一台机器,每个题目只有在所有数据点全部正确后才能得到分数。比赛过程中可以有多次提交机会,实时评测并返回结果。比赛排名根据做题数和罚时来评判,罚时是通过题目的用时之和加上错误提交次数乘以一个系数。在 ACM 相关赛事中,选手允许带纸质资料。 ### Codeforces (CF) 赛制 -[Codeforces](https://codeforces.com) 是一个在线评测系统,定期会举办比赛。它的比赛特点是在比赛过程中只测试一部分数据(pretests),而在比赛结束后返回完整的所有测试点的测试结果(system tests)。比赛时可以多次提交,允许 hack 别人的代码(此处 hack 的意思是提交一个测试数据,使得别人的代码无法给出正确答案)。当然,如果想要 hack ,必须要锁定自己的代码(换言之,比赛时无法重新提交该题)。 +[Codeforces](https://codeforces.com)是一个在线评测系统,定期会举办比赛。它的比赛特点是在比赛过程中只测试一部分数据(pretests),而在比赛结束后返回完整的所有测试点的测试结果(system tests)。比赛时可以多次提交,允许 hack 别人的代码(此处 hack 的意思是提交一个测试数据,使得别人的代码无法给出正确答案)。当然,如果想要 hack,必须要锁定自己的代码(换言之,比赛时无法重新提交该题)。 ## 其他国家和地区的 OI 竞赛 @@ -84,17 +84,17 @@ USACO 或许是国内选手最熟悉的外国 OI 竞赛(因此可能也是中 ### 波兰:POI 官网: -官方提交地址: +官方提交地址: POI 是不少省选选手最常刷的外国 OI 比赛。 -根据(已经凉凉的) 的描述,POI 的流程如下: +根据(已经凉凉的)的描述,POI 的流程如下: - 第一轮:五题,网络赛,公开赛。 - 第二轮:包含一场练习赛,和两场正式比赛。 - 第三轮:赛制同上。 - ONTAK:POI 训练营(类似国内的集训队) -你可能还听说过 PA。PA 的大意是 “算法大战”(我也不知道为啥它叫这名字)。 +你可能还听说过 PA。PA 的大意是“算法大战”(我也不知道为啥它叫这名字)。 目前在国内 OJ 中,POI 题目最全的是 BZOJ。 @@ -115,18 +115,18 @@ POI 是不少省选选手最常刷的外国 OI 比赛。 JOI 的流程: - 预赛(予選) -- 决赛(本選 / JOI Final) -- 春训营(春季トレーニング合宿 / JOI Spring Camp / JOISC) -- 公开赛(通信教育 / JOI Open Contest) +- 决赛(本選/JOI Final) +- 春训营(春季トレーニング合宿/JOI Spring Camp/JOISC) +- 公开赛(通信教育/JOI Open Contest) 目前 LibreOJ 和 BZOJ 有近些年的 JOI Final、JOISC 和 JOI Open 的题目。UOJ 有部分 JOISC 2017 的题目。 JOI Final 的难度在提高 - $\sim$ 提高 + 左右。JOISC 和 JOI Open 的题目的难度在提高 $\sim$ NOI - 不等。 -你可以在 JOI 官网或者 AtCoder 上找到更多的 JOI 题 (日文题面) +你可以在 JOI 官网或者 AtCoder 上找到更多的 JOI 题(日文题面) ### 台湾地区:資訊奧林匹亞競賽 -台湾地区把 OI 中的 informatics 翻译成 “资讯” 而非大陆通用的翻译 “信息”。 -台湾地区的选手如果想去参加 IOI,需要经过这几场比赛的洗礼: +台湾地区把 OI 中的 informatics 翻译成“资讯”而非大陆通用的翻译“信息”。 +台湾地区的选手如果想去参加 IOI,需要经过这几场比赛的洗礼: - 區域資訊學科能力競賽 - 全國資訊學科能力競賽 @@ -139,9 +139,9 @@ JOI Final 的难度在提高 - $\sim$ 提高 + 左右。JOISC 和 JOI Open 的 在线提交地址: 一般简称为 ROI。流程: -- 市级比赛(Municipal stage / Муниципальный этап) -- 地区级比赛(Regional Stage / Региональный этап) -- 决赛(Final Stage / Заключительный этап) +- 市级比赛(Municipal stage/Муниципальный этап) +- 地区级比赛(Regional Stage/Региональный этап) +- 决赛(Final Stage/Заключительный этап) 你可能已经在 Codeforces 上见过了一些 ROI 题。目前 LibreOJ 有近两年的 ROI 决赛题(包括翻译)。 @@ -150,10 +150,10 @@ JOI Final 的难度在提高 - $\sim$ 提高 + 左右。JOISC 和 JOI Open 的 CCC: Canadian Computing Competition CCO: Canadian Computing Olympiad 官网地址: -CCC 提交地址: -CCO 提交地址: +CCC 提交地址:[https://dmoj.ca/problems/?category=4](https://dmoj.ca/problems/?category=4) +CCO 提交地址:[https://dmoj.ca/problems/?category=24](https://dmoj.ca/problems/?category=24) CCC 在 DMOJ 有官方 (?) 题解。 -CCC Junior / Senior 贴近 NOIP 普及组 / 提高组难度。CCO 想要拿到金牌可能得有 NOI 银的水平。 +CCC Junior/Senior 贴近 NOIP 普及组/提高组难度。CCO 想要拿到金牌可能得有 NOI 银的水平。 ### 法国与澳大利亚:FARIO @@ -166,7 +166,7 @@ FARIO 的题目与 NOI 的难度旗鼓相当。 ### BalticOI BalticOI 面向的是波罗的海周边各国。BalticOI 2018 的参赛国有立陶宛、波兰、爱沙尼亚、芬兰等 9 国。 -除了 2017 年,BalticOI 每年都公开题面、测试数据和题解。然而 BalticOI 没有一个固定的官网,每年的主办方都会新建一个网站…… 关于历年的官网地址,Planet6174 整理出了一个[帖子](https://loj.ac/article/416)。 +除了 2017 年,BalticOI 每年都公开题面、测试数据和题解。然而 BalticOI 没有一个固定的官网,每年的主办方都会新建一个网站……关于历年的官网地址,Planet6174 整理出了一个[帖子](https://loj.ac/article/416)。 目前 LibreOJ 有近十年的 BalticOI 题。 ### BalkanOI diff --git a/docs/intro/non-traditional.md b/docs/intro/non-traditional.md index 8d13c1fb..d75e7569 100644 --- a/docs/intro/non-traditional.md +++ b/docs/intro/non-traditional.md @@ -19,15 +19,15 @@ ### STDIO 交互 -STDIO 交互(标准 I/O 交互)是 Codeforces、AtCoder 等在线平台的交互手段,也是 ICPC 系列赛事中的标准。[LOJ #559. 「LibreOJ Round #9」ZQC 的迷宫](https://loj.ac/problem/559) 是一道典型的 STDIO 交互题。[这里](https://codeforces.com/blog/entry/45307) 是 Codeforces 的一个更加简要的说明。 +STDIO 交互(标准 I/O 交互)是 Codeforces、AtCoder 等在线平台的交互手段,也是 ICPC 系列赛事中的标准。[LOJ #559.「LibreOJ Round #9」ZQC 的迷宫](https://loj.ac/problem/559)是一道典型的 STDIO 交互题。[这里](https://codeforces.com/blog/entry/45307)是 Codeforces 的一个更加简要的说明。 -对于这类题目,选手只需像往常一样将询问写到标准输出,**刷新输出缓冲**后从标准输入读取结果。选手程序刷新输出缓冲后,通过管道连接它的测评程序(称为交互器)才能立刻接收到这些数据。在 C/C++ 中,`fflush(stdout)` 和 `std::cout << std::flush` 可以实现这个操作;Pascal 则是 `flush(output)`。 +对于这类题目,选手只需像往常一样将询问写到标准输出,**刷新输出缓冲**后从标准输入读取结果。选手程序刷新输出缓冲后,通过管道连接它的测评程序(称为交互器)才能立刻接收到这些数据。在 C/C++ 中, `fflush(stdout)` 和 `std::cout << std::flush` 可以实现这个操作;Pascal 则是 `flush(output)` 。 ### Grader 交互 -Grader 交互方式常见于 IOI、APIO 等国际 OI 赛事(特别是 CMS 平台的竞赛)。[UOJ #206. 【APIO2016】Gap](http://uoj.ac/problem/206) 是一道典型的 grader 交互题。 +Grader 交互方式常见于 IOI、APIO 等国际 OI 赛事(特别是 CMS 平台的竞赛)。[UOJ #206.【APIO2016】Gap](http://uoj.ac/problem/206)是一道典型的 grader 交互题。 -对于这类题目,选手只需编写一个特定的函数完成某项任务,它通过调用给定的若干辅助函数来进行交互。为了便于选手在本地测试,题目会下发一个头文件与一个参考测评程序 `grader.cpp`(对于 Pascal 语言是一个库 `graderlib`),选手将自己的程序与 `grader.cpp` 一同编译方可得到可执行文件。 +对于这类题目,选手只需编写一个特定的函数完成某项任务,它通过调用给定的若干辅助函数来进行交互。为了便于选手在本地测试,题目会下发一个头文件与一个参考测评程序 `grader.cpp` (对于 Pascal 语言是一个库 `graderlib` ),选手将自己的程序与 `grader.cpp` 一同编译方可得到可执行文件。 ```sh g++ grader.cpp my_solution.cpp -o my_solution -Wall -O2 @@ -36,7 +36,7 @@ g++ grader.cpp my_solution.cpp -o my_solution -Wall -O2 编译得到的程序表现与传统题程序类似。它会打开固定的文件,以固定的格式读取数据,调用选手编写的函数,并将结果和若干信息(例如询问的次数、答案正确性)显示在标准输出上。 -实际测评时,选手的程序会与一个不同的 `grader.cpp` 编译。这个 `grader.cpp` 将以类似的方式调用选手编写的函数,并记录其得分。一般来说,这个版本的 `grader.cpp` 所有全局符号都会设为 `static`,也即不能通过冲突命名的方式破解它,但是任何尝试突破 grader 限制的行为都是会导致 disqualification 的哦。 +实际测评时,选手的程序会与一个不同的 `grader.cpp` 编译。这个 `grader.cpp` 将以类似的方式调用选手编写的函数,并记录其得分。一般来说,这个版本的 `grader.cpp` 所有全局符号都会设为 `static` ,也即不能通过冲突命名的方式破解它,但是任何尝试突破 grader 限制的行为都是会导致 disqualification 的哦。 ### 差别 @@ -46,12 +46,12 @@ STDIO 交互的一个明显优势在于它可以支持任何编程语言,但 ## 通信题 -在通信题中,选手需要编写**两个**程序,合作完成某项任务。第一个程序接收问题的输入,并产生某些输出;第二个程序的输入会与第一个的输出相关(有时是原封不动地作为一个参数,有时会由评测端处理得到),它需要产生问题的解。[UOJ #178. 新年的贺电](http://uoj.ac/problem/178) 和 [#454. 【UER #8】打雪仗](http://uoj.ac/problem/454) 都是典型的通信题。 +在通信题中,选手需要编写**两个**程序,合作完成某项任务。第一个程序接收问题的输入,并产生某些输出;第二个程序的输入会与第一个的输出相关(有时是原封不动地作为一个参数,有时会由评测端处理得到),它需要产生问题的解。[UOJ #178. 新年的贺电](http://uoj.ac/problem/178)和[#454.【UER #8】打雪仗](http://uoj.ac/problem/454)都是典型的通信题。 本地测试的方法由于题目设定的不同而多种多样,常用的如: - 手工输入 - 编写一个辅助程序,转换第一个程序的输出到第二个程序的输入 -- 用双向管道将两个程序的标准输入 / 输出连接起来 +- 用双向管道将两个程序的标准输入/输出连接起来 由于评测平台对于通信题的支持有限,因而目前为止通信题只常见于 IOI 系列赛和 UOJ 等少数在线平台举办的比赛,仍是一个有待探索的领域。期待着在未来,通信题能带来更多全新的挑战和新鲜的 idea。 diff --git a/docs/intro/resources.md b/docs/intro/resources.md index f6b01376..7cea2a63 100644 --- a/docs/intro/resources.md +++ b/docs/intro/resources.md @@ -1,94 +1,94 @@ -## 在线测试 (训练) 平台 +## 在线测试(训练)平台 在线测试平台又称 Online Judging System,一般用来刷题、组织比赛,也有的会提供博客功能方便选手交流。 ### 国内 -- [51Nod](https://www.51nod.com/) (有很多好的数学题和思维题) -- [BZOJ](https://www.lydsy.com/JudgeOnline/) (优质的题巨多) +- [51Nod](https://www.51nod.com/)(有很多好的数学题和思维题) +- [BZOJ](https://www.lydsy.com/JudgeOnline/)(优质的题巨多) - [CodeVS](http://www.codevs.cn/) -- [FZUOJ](http://acm.fzu.edu.cn/) (福州大学) -- [HDU Online Judge](http://acm.hdu.edu.cn/) (杭电的 OJ,多校训练的题目放在这里) +- [FZUOJ](http://acm.fzu.edu.cn/)(福州大学) +- [HDU Online Judge](http://acm.hdu.edu.cn/)(杭电的 OJ,多校训练的题目放在这里) - [hihoCoder](https://hihocoder.com/) - [计蒜客](https://www.jisuanke.com/) -- [Judge Duck Online](https://duck.ac/) (松松松的 OJ,精确到 $\mu s$) -- [JoyOI](http://www.joyoi.cn/) (原 Tyvj) +- [Judge Duck Online](https://duck.ac/)(松松松的 OJ,精确到 $\mu s$ ) +- [JoyOI](http://www.joyoi.cn/)(原 Tyvj) - [LibreOJ](https://loj.ac/) -- [洛谷](http://www.luogu.org/) (常用 OJ,现代 OJ 支持发布比赛等很多功能,评测机快) +- [洛谷](http://www.luogu.org/)(常用 OJ,现代 OJ 支持发布比赛等很多功能,评测机快) - [牛客网](https://www.nowcoder.com/) - [OpenJudge](http://openjudge.cn/) -- [POJ](http://poj.org/) (PKU OJ,国内历史最悠久的 OJ 之一,很多英文题,有一些基础题和好题) -- [Universal Online Judge](http://uoj.ac/) (VFK 的 OJ,多原创比赛题和 CCF/THU 题 难度较高) +- [POJ](http://poj.org/)(PKU OJ,国内历史最悠久的 OJ 之一,很多英文题,有一些基础题和好题) +- [Universal Online Judge](http://uoj.ac/)(VFK 的 OJ,多原创比赛题和 CCF/THU 题 难度较高) - [Vijos](https://vijos.org/) -- [Virtual Judge](https://vjudge.net/) (可以方便的在 Vjudge 上提交别的 OJ 的题,尤其是一些国内不太方便的 OJ) -- [ZOJ](http://acm.zju.edu.cn/onlinejudge/) (浙大) +- [Virtual Judge](https://vjudge.net/)(可以方便的在 Vjudge 上提交别的 OJ 的题,尤其是一些国内不太方便的 OJ) +- [ZOJ](http://acm.zju.edu.cn/onlinejudge/)(浙大) ### 国外 -- [AtCoder](https://atcoder.jp/) (日本的一个 OJ,类似 Codeforces 但是也会放 JOI 的题) -- [CodeChef](https://codechef.com/) (印度 OJ,周期性有比赛) -- [Codeforces](https://codeforces.com/) (俄罗斯 OJ,有很多比赛) +- [AtCoder](https://atcoder.jp/)(日本的一个 OJ,类似 Codeforces 但是也会放 JOI 的题) +- [CodeChef](https://codechef.com/)(印度 OJ,周期性有比赛) +- [Codeforces](https://codeforces.com/)(俄罗斯 OJ,有很多比赛) - [CS Academy](https://csacademy.com/) -- [DMOJ](https://dmoj.ca/) (加拿大开源的 OJ,语言支持广;题库是各大比赛的存档,也有定期自行举办的比赛) -- [HackerRank](https://www.hackerrank.com/) (有很多比赛) +- [DMOJ](https://dmoj.ca/)(加拿大开源的 OJ,语言支持广;题库是各大比赛的存档,也有定期自行举办的比赛) +- [HackerRank](https://www.hackerrank.com/)(有很多比赛) - [Kattis](https://open.kattis.com/) -- [LeetCode](https://leetcode.com/) (有中文分站:[LeetCode China](https://leetcode-cn.com/)) +- [LeetCode](https://leetcode.com/)(有中文分站:[LeetCode China](https://leetcode-cn.com/)) - [SPOJ](http://www.spoj.com) -- [Topcoder](https://www.topcoder.com/) (有很多比赛) +- [Topcoder](https://www.topcoder.com/)(有很多比赛) - [Ural](http://acm.timus.ru/) -- [UVaOJ](https://uva.onlinejudge.org/) (做 lrj 的书怎么可能不知道这个 OJ) -- [Yandex](https://contest.yandex.ru/) (存档了近几年的全俄罗斯信息学奥赛) -- [Light OJ](http://lightoj.com)(一个快挂了的 OJ,`www`域名无法访问,请使用[根域名](http://lightoj.com)访问) +- [UVaOJ](https://uva.onlinejudge.org/)(做 lrj 的书怎么可能不知道这个 OJ) +- [Yandex](https://contest.yandex.ru/)(存档了近几年的全俄罗斯信息学奥赛) +- [Light OJ](http://lightoj.com)(一个快挂了的 OJ, `www` 域名无法访问,请使用[根域名](http://lightoj.com)访问) ## 教程 - [OI Wiki](https://oi-wiki.org) - [Codeforces 上网友整理的一份教程合集](http://codeforces.com/blog/entry/57282) - [英文版 E-Maxx 算法教程](https://cp-algorithms.com/) -- [演算法笔记](http://www.csie.ntnu.edu.tw/~u91029/) (台湾师范大学总结的教程) +- [演算法笔记](http://www.csie.ntnu.edu.tw/~u91029/)(台湾师范大学总结的教程) - [algo.is](https://algo.is/t-414-aflv-competitive-programming-course-2016/) -- [CS 97SI: Introduction to Programming Contests](http://web.stanford.edu/class/cs97si/) (斯坦福的一门课) -- [如何为 ACM-ICPC 做准备? - geeksforgeeks](https://www.geeksforgeeks.org/how-to-prepare-for-acm-icpc/) +- [CS 97SI: Introduction to Programming Contests](http://web.stanford.edu/class/cs97si/)(斯坦福的一门课) +- [如何为 ACM-ICPC 做准备?- geeksforgeeks](https://www.geeksforgeeks.org/how-to-prepare-for-acm-icpc/) - [Topcoder 整理的教程](https://www.topcoder.com/community/competitive-programming/tutorials/) - [校招面试指南](https://github.com/jwasham/coding-interview-university) ## 书籍 -- 《信息学奥赛一本通》 (初学者向) -- 《算法竞赛入门经典》 (紫书) - - [第一版 配套资源仓库 (镜像)](https://github.com/sukhoeing/aoapc-book/) +- 《信息学奥赛一本通》(初学者向) +- 《算法竞赛入门经典》(紫书) + - [第一版 配套资源仓库(镜像)](https://github.com/sukhoeing/aoapc-book/) - [第二版 配套资源仓库](https://github.com/aoapc-book/aoapc-bac2nd) - [第二版 习题选解](https://github.com/sukhoeing/aoapc-bac2nd-keys) -- 《算法竞赛入门经典——训练指南》 (大白) -- 《算法艺术与信息学竞赛》 (蓝书 / 黑书) +- 《算法竞赛入门经典——训练指南》(大白) +- 《算法艺术与信息学竞赛》(蓝书/黑书) - 《算法竞赛进阶指南》 - [配套资源仓库](https://github.com/lydrainbowcat/tedukuri) - 《具体数学》 - _[Competitive Programmer's Handbook](https://cses.fi/book/index.html)_ -- 《算法导论》 (黑书,大学经典教材) +- 《算法导论》(黑书,大学经典教材) - [答案解析 (English)](https://github.com/walkccc/CLRS) - 《啊哈算法》 -- 《挑战程序设计竞赛》全套 (通俗易懂) -- 《算法概论》 (提纲挚领,但内容较少) +- 《挑战程序设计竞赛》全套(通俗易懂) +- 《算法概论》(提纲挚领,但内容较少) - [Legend-K 的数据结构与算法的笔记](http://www.legend-k.com/Algorithm/Algorithm.pdf) - [acm-cheat-sheet](https://github.com/soulmachine/acm-cheat-sheet) ## 工具 -- [VisuAlgo](https://visualgo.net/en) (经典算法的可视化结果) -- [USF](https://www.cs.usfca.edu/~galles/visualization/) (算法可视化) +- [VisuAlgo](https://visualgo.net/en)(经典算法的可视化结果) +- [USF](https://www.cs.usfca.edu/~galles/visualization/)(算法可视化) - [Algomation](http://www.algomation.com/) - [Algorithm Visualizer](http://algorithm-visualizer.org) -- [OEIS](https://oeis.org) (整数数列搜索引擎) -- [Ubuntu Pastebin](https://paste.ubuntu.com) (可以用来分享代码) -- [uDebug](https://www.udebug.com) (提供一些 OJ 题目的调试辅助) -- [cppreference.com](https://zh.cppreference.com/w/) (提供 C++ 内语法的查询等) -- [图论画板](https://csacademy.com/app/graph_editor/) (同时推荐 [GraphViz](http://www.graphviz.org/)) -- [$\LaTeX{}$ 数学公式参考](http://www.mohu.org/info/symbols/symbols.htm) -- [Godbolt](https://godbolt.org/) (在浏览器中查看编译后代码块对应的汇编语句) +- [OEIS](https://oeis.org)(整数数列搜索引擎) +- [Ubuntu Pastebin](https://paste.ubuntu.com)(可以用来分享代码) +- [uDebug](https://www.udebug.com)(提供一些 OJ 题目的调试辅助) +- [cppreference.com](https://zh.cppreference.com/w/)(提供 C++ 内语法的查询等) +- [图论画板](https://csacademy.com/app/graph_editor/)(同时推荐[GraphViz](http://www.graphviz.org/)) +- [ $\LaTeX{}$ 数学公式参考](http://www.mohu.org/info/symbols/symbols.htm) +- [Godbolt](https://godbolt.org/)(在浏览器中查看编译后代码块对应的汇编语句) - [《100 个 gdb 小技巧》](https://github.com/hellogcc/100-gdb-tips) -- [Mathpix](https://mathpix.com/) (截图转 $\LaTeX{}$) -- [$\LaTeX{}$ 手写符号识别](http://detexify.kirelabs.org/classify.html) +- [Mathpix](https://mathpix.com/)(截图转 $\LaTeX{}$ ) +- [ $\LaTeX{}$ 手写符号识别](http://detexify.kirelabs.org/classify.html) ## 题集 diff --git a/docs/intro/spj.md b/docs/intro/spj.md index 38e1006e..e52196f5 100644 --- a/docs/intro/spj.md +++ b/docs/intro/spj.md @@ -1,25 +1,25 @@ -一道题如果有多组解,我们就需要一个程序来判断答案合法性,这便是 Special Judge (spj),又常被称作 checker,下面介绍部分评测工具 / OJ 的 spj 编写方法。 +一道题如果有多组解,我们就需要一个程序来判断答案合法性,这便是 Special Judge (spj),又常被称作 checker,下面介绍部分评测工具/OJ 的 spj 编写方法。 ???+ warning spj 还应当判断文件尾是否有多余内容,及输出格式是否正确(如题目要求数字间用一个空格隔开,而选手却使用了换行)。但是,目前前者只有 Testlib 可以方便地做到这一点,而后者几乎无人去特意进行这种判断。 浮点数时应注意 nan,不合理的判断方式会导致输出 nan 即可 AC。 -以下均使用 C++,以 “要求标准答案与选手答案差值小于 1e-3,文件名为 num” 为例。 +以下均使用 C++,以“要求标准答案与选手答案差值小于 1e-3,文件名为 num”为例。 ## Testlib -Testlib 的介绍见 [Testlib / 简介](/intro/testlib/) 页面,用法见 [Testlib / Checker](/intro/testlib/checker/) 页面。 +Testlib 的介绍见[Testlib/简介](/intro/testlib/)页面,用法见[Testlib/Checker](/intro/testlib/checker/)页面。 -必须使用 Testlib 做 spj 的 评测工具 / OJ:Codeforces、洛谷、UOJ 等 +必须使用 Testlib 做 spj 的 评测工具/OJ:Codeforces、洛谷、UOJ 等 -可以使用 Testlib 做 spj 的 评测工具 / OJ:LibreOJ(SYZOJ 2)、Lemon 等 +可以使用 Testlib 做 spj 的 评测工具/OJ:LibreOJ(SYZOJ 2)、Lemon 等 -SYZOJ 2 所需的修改版 Testlib 可以在[这里](https://pastebin.com/3GANXMG7)获取到,感谢 [cyand1317](https://loj.ac/article/124)。 +SYZOJ 2 所需的修改版 Testlib 可以在[这里](https://pastebin.com/3GANXMG7)获取到,感谢[cyand1317](https://loj.ac/article/124)。 -Lemon 所需的修改版 Testlib 可以在[这里](https://paste.ubuntu.com/p/JsTspHHnmB/)获取到,感谢 matthew99。注意此版本 Testlib 注册 checker 应使用 `registerLemonChecker()` 而非 `registerTestlibCmd()`。 +Lemon 所需的修改版 Testlib 可以在[这里](https://paste.ubuntu.com/p/JsTspHHnmB/)获取到,感谢 matthew99。注意此版本 Testlib 注册 checker 应使用 `registerLemonChecker()` 而非 `registerTestlibCmd()` 。 -其他评测工具 / OJ 大部分需要按照其 spj 编写格式修改 Testlib(并将 testlib.h 与 spj 一同上传,或将 testlib.h 置于 include 目录)。 +其他评测工具/OJ 大部分需要按照其 spj 编写格式修改 Testlib(并将 testlib.h 与 spj 一同上传,或将 testlib.h 置于 include 目录)。 ```cpp #include @@ -41,7 +41,7 @@ int main(int argc, char *argv[]) { ## Lemon -**Lemon 有现成的修改版 Testlib,建议使用 Testlib,见 [Testlib](#testlib)** +**Lemon 有现成的修改版 Testlib,建议使用 Testlib,见[Testlib](#testlib)** ```cpp #include @@ -195,7 +195,7 @@ int main(int argc, char* argv[]) { ## LibreOJ(SYZOJ 2) -**LibreOJ(SYZOJ 2) 有现成的修改版 Testlib,建议使用 Testlib,见 [Testlib](#testlib)** +**LibreOJ(SYZOJ 2) 有现成的修改版 Testlib,建议使用 Testlib,见[Testlib](#testlib)** ```cpp #include diff --git a/docs/intro/testlib/checker.md b/docs/intro/testlib/checker.md index ad58a897..f976272d 100644 --- a/docs/intro/testlib/checker.md +++ b/docs/intro/testlib/checker.md @@ -1,4 +1,4 @@ -Checker,即 [Special Judge](/intro/spj),用于检验答案是否合法。使用 Testlib 可以让我们免去检验许多东西,使编写简单许多。 +Checker,即[Special Judge](/intro/spj),用于检验答案是否合法。使用 Testlib 可以让我们免去检验许多东西,使编写简单许多。 Checker 从命令行参数读取到输入文件名、选手输出文件名、标准输出文件名,并确定选手输出是否正确,并返回一个预定义的结果: @@ -7,7 +7,7 @@ Checker 从命令行参数读取到输入文件名、选手输出文件名、标 ## 简单的例子 ???+note 题目 - 给定两个整数 $a,b$($-1000 \le a,b \le 1000$),输出它们的和。 + 给定两个整数 $a,b$ ( $-1000 \le a,b \le 1000$ ),输出它们的和。 这题显然不需要 checker 对吧,但是如果一定要的话也可以写一个: @@ -194,7 +194,7 @@ int main(int argc, char* argv[]) { ``` ???+ warning - 请在 `readAns` 中避免调用**全局**函数 `::ensure/ensuref()`,这会导致在某些应判为 Wrong Answer 的选手输出下返回 `_fail`,产生错误。 + 请在 `readAns` 中避免调用**全局**函数 `::ensure/ensuref()` ,这会导致在某些应判为 Wrong Answer 的选手输出下返回 `_fail` ,产生错误。 ## 建议与常见错误 @@ -232,4 +232,4 @@ int main(int argc, char* argv[]) { - 使用项别名 -**本文翻译自[Checkers with testlib.h - Codeforces](https://codeforces.com/blog/entry/18431)。`testlib.h` 的 GitHub 存储库为[MikeMirzayanov/testlib](https://github.com/MikeMirzayanov/testlib)。** +**本文翻译自[Checkers with testlib.h - Codeforces](https://codeforces.com/blog/entry/18431)。 `testlib.h` 的 GitHub 存储库为[MikeMirzayanov/testlib](https://github.com/MikeMirzayanov/testlib)。** diff --git a/docs/intro/testlib/general.md b/docs/intro/testlib/general.md index 47b3a8cc..804b6944 100644 --- a/docs/intro/testlib/general.md +++ b/docs/intro/testlib/general.md @@ -1,83 +1,83 @@ -本页面介绍 Testlib checker/interactor/validator 的一些通用状态 / 对象 / 函数。 +本页面介绍 Testlib checker/interactor/validator 的一些通用状态/对象/函数。 ## 通用状态 -| 结果 | Testlib 别名 | 含义 | -| ------------------ | ------------ | ---------------------------------------------------------------------------------------- | -| Ok | `_ok` | 答案正确。 | -| Wrong Answer | `_wa` | 答案错误。 | -| Presentation Error | `_pe` | 答案格式错误。注意包括 Codeforces 在内的许多 OJ 并不区分 PE 和 WA。 | -| Partially Correct | `_pc(score)` | 答案部分正确。仅限于有部分分的测试点,其中 `score` 为一个正整数,从 $0$(没分)到 $100$(可能的最大分数)。 | -| Fail | `_fail` | validator 中表示输入不合法,不通过校验。
checker 中表示程序内部错误、标准输出有误或选手输出比标准输出更优,需要裁判 / 出题人关注。(也就是题目锅了) | +| 结果 | Testlib 别名 | 含义 | +| ------------------ | -------------- | -------------------------------------------------------------------------------------- | +| Ok | `_ok` | 答案正确。 | +| Wrong Answer | `_wa` | 答案错误。 | +| Presentation Error | `_pe` | 答案格式错误。注意包括 Codeforces 在内的许多 OJ 并不区分 PE 和 WA。 | +| Partially Correct | `_pc(score)` | 答案部分正确。仅限于有部分分的测试点,其中 `score` 为一个正整数,从 $0$ (没分)到 $100$ (可能的最大分数)。 | +| Fail | `_fail` | validator 中表示输入不合法,不通过校验。
checker 中表示程序内部错误、标准输出有误或选手输出比标准输出更优,需要裁判/出题人关注。(也就是题目锅了) | -通常用程序的返回值表明结果,但是也有一些其他方法:创建一个输出 xml 文件、输出信息到 stdout 或其他位置…… 这些都通过下方函数表中的 `quitf` 函数来完成。 +通常用程序的返回值表明结果,但是也有一些其他方法:创建一个输出 xml 文件、输出信息到 stdout 或其他位置……这些都通过下方函数表中的 `quitf` 函数来完成。 ## 通用对象 -| 对象 | 含义 | -| ----- | ----- | -| `inf` | 输入文件流 | -| `ouf` | 选手输出流 | -| `ans` | 参考输出流 | +| 对象 | 含义 | +| ------- | ----- | +| `inf` | 输入文件流 | +| `ouf` | 选手输出流 | +| `ans` | 参考输出流 | ## 通用函数 非成员函数: -| 调用 | 含义 | -| ----------------------------------------------------------------------------------------------- | --------------------------------------------------- | -| `void registerTestlibCmd(int argc, char* argv[])` | 注册程序为 checker | -| `void registerInteraction(int argc, char* argv[])` | 注册程序为 interactor | -| `void registerValidation()`/`void registerValidation(int argc, char* argv[])` | 注册程序为 validator | -| `void registerGen(int argc, char* argv[], int randomGeneratorVersion)` | 注册程序为 generator
`randomGeneratorVersion` 推荐为 `1` | -| `void quit(TResult verdict, string message)`/`void quitf(TResult verdict, string message, ...)` | 结束程序,返回 `verdict`,输出 `message` | -| `void quitif(bool condition, TResult verdict, string message, ...)` | 如果 `condition` 成立,调用 `quitf(verdict, message, ...)` | +| 调用 | 含义 | +| --------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | +| `void registerTestlibCmd(int argc, char* argv[])` | 注册程序为 checker | +| `void registerInteraction(int argc, char* argv[])` | 注册程序为 interactor | +| `void registerValidation()` / `void registerValidation(int argc, char* argv[])` | 注册程序为 validator | +| `void registerGen(int argc, char* argv[], int randomGeneratorVersion)` | 注册程序为 generator
`randomGeneratorVersion` 推荐为 `1` | +| `void quit(TResult verdict, string message)` / `void quitf(TResult verdict, string message, ...)` | 结束程序,返回 `verdict` ,输出 `message` | +| `void quitif(bool condition, TResult verdict, string message, ...)` | 如果 `condition` 成立,调用 `quitf(verdict, message, ...)` | 流成员函数: -| 调用 | 含义 | -| ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | -| `char readChar()` | 读入一个字符 | -| `char readChar(char c)` | 读入一个字符,必须为 `c` | -| `char readSpace()` | 等同于 `readChar(' ')` | -| `string readToken()`/`string readWord()` | 读入一个串,到空白字符(空格、Tab、EOLN 等)停止 | -| `string readToken(string regex)`/`string readWord(string regex)` | 读入一个串,必须与 `regex` 匹配 | -| `long long readLong()` | 读入一个 64 位整数 | -| `long long readLong(long long L, long long R)` | 读入一个 64 位整数,必须在 $[L,R]$ 之间 | -| `vector readLongs(int n, long long L, long long R)` | 读入 $N$ 个 64 位整数,必须均在 $[L,R]$ 之间 | -| `int readInt()`/`int readInteger()` | 读入一个 32 位整数 | -| `int readInt(int L, int R)`/`int readInteger(L, R)` | 读入一个 32 位整数,必须在 $[L,R]$ 之间 | -| `vector readInts(int n, int L, int R)`/`vector readIntegers(int n, int L, int R)` | 读入 $N$ 个 32 位整数,必须均在 $[L,R]$ 之间 | -| `double readReal()`/`double readDouble()` | 读入一个双精度浮点数 | -| `double readReal(double L, double R)`/`double readDouble(double L, double R)` | 读入一个双精度浮点数,必须在 $[L,R]$ 之间 | -| `double readStrictReal(double L, double R, int minPrecision, int maxPrecision)`/`double readStrictDouble(double L, double R, int minPrecision, int maxPrecision)` | 读入一个双精度浮点数,必须在 $[L,R]$ 之间,小数位数必须在 $[minPrecision,maxPrecision]$ 之间,不得使用指数计数法等非正常格式 | -| `string readString()`/`string readLine()` | 读入一行 | -| `string readString(string regex)`/`string readLine(string regex)` | 读入一行,必须与 `regex` 匹配 | -| `void readEoln()` | 读入 EOLN | -| `void readEof()` | 读入 EOF | -| `void quit(TResult verdict, string message)`/`void quitf(TResult verdict, string message, ...)` | 结束程序,若 `Stream` 为 `ouf` 返回 `verdict`,否则返回 `_fail`;输出 `message` | -| `void quitif(bool condition, TResult verdict, string message, ...)` | 如果 `condition` 成立,调用 `quitf(verdict, message, ...)` | - -未完待续... +| 调用 | 含义 | +| --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | +| `char readChar()` | 读入一个字符 | +| `char readChar(char c)` | 读入一个字符,必须为 `c` | +| `char readSpace()` | 等同于 `readChar(' ')` | +| `string readToken()` / `string readWord()` | 读入一个串,到空白字符(空格、Tab、EOLN 等)停止 | +| `string readToken(string regex)` / `string readWord(string regex)` | 读入一个串,必须与 `regex` 匹配 | +| `long long readLong()` | 读入一个 64 位整数 | +| `long long readLong(long long L, long long R)` | 读入一个 64 位整数,必须在 $[L,R]$ 之间 | +| `vector readLongs(int n, long long L, long long R)` | 读入 $N$ 个 64 位整数,必须均在 $[L,R]$ 之间 | +| `int readInt()` / `int readInteger()` | 读入一个 32 位整数 | +| `int readInt(int L, int R)` / `int readInteger(L, R)` | 读入一个 32 位整数,必须在 $[L,R]$ 之间 | +| `vector readInts(int n, int L, int R)` / `vector readIntegers(int n, int L, int R)` | 读入 $N$ 个 32 位整数,必须均在 $[L,R]$ 之间 | +| `double readReal()` / `double readDouble()` | 读入一个双精度浮点数 | +| `double readReal(double L, double R)` / `double readDouble(double L, double R)` | 读入一个双精度浮点数,必须在 $[L,R]$ 之间 | +| `double readStrictReal(double L, double R, int minPrecision, int maxPrecision)` / `double readStrictDouble(double L, double R, int minPrecision, int maxPrecision)` | 读入一个双精度浮点数,必须在 $[L,R]$ 之间,小数位数必须在 $[minPrecision,maxPrecision]$ 之间,不得使用指数计数法等非正常格式 | +| `string readString()` / `string readLine()` | 读入一行 | +| `string readString(string regex)` / `string readLine(string regex)` | 读入一行,必须与 `regex` 匹配 | +| `void readEoln()` | 读入 EOLN | +| `void readEof()` | 读入 EOF | +| `void quit(TResult verdict, string message)` / `void quitf(TResult verdict, string message, ...)` | 结束程序,若 `Stream` 为 `ouf` 返回 `verdict` ,否则返回 `_fail` ;输出 `message` | +| `void quitif(bool condition, TResult verdict, string message, ...)` | 如果 `condition` 成立,调用 `quitf(verdict, message, ...)` | + +未完待续…… ## 极简正则表达式 -一些函数允许使用 “极简正则表达式” 特性,如下所示: +一些函数允许使用“极简正则表达式”特性,如下所示: -- 字符集。如 `[a-z]` 表示所有小写英文字母,`[^a-z]` 表示除小写英文字母外任何字符。 +- 字符集。如 `[a-z]` 表示所有小写英文字母, `[^a-z]` 表示除小写英文字母外任何字符。 - 范围。如 `[a-z]{1,5}` 表示一个长度在 $[1,5]$ 范围内且只包含小写英文字母的串。 -- “或” 标识符。如 `mike|john` 表示 `mike` 或 `john` 其一。 -- “可选” 标识符。如 `-?[1-9][0-9]{0,3}` 表示 $[-9999,9999]$ 范围内的整数(注意那个可选的负号)。 -- “重复” 标识符。如 `[0-9]*` 表示 $0$ 个或更多数字,`[0-9]+`表示 $1$ 个或更多数字。 -- 注意这里的正则表达式是 “贪婪” 的(“重复” 会尽可能匹配)。如 `[0-9]?1` 将不会匹配 `1`(因为 `[0-9]?` 将 `1` 匹配上,导致模板串剩余的那个 `1` 无法匹配)。 +- “或”标识符。如 `mike|john` 表示 `mike` 或 `john` 其一。 +- “可选”标识符。如 `-?[1-9][0-9]{0,3}` 表示 $[-9999,9999]$ 范围内的整数(注意那个可选的负号)。 +- “重复”标识符。如 `[0-9]*` 表示 $0$ 个或更多数字, `[0-9]+` 表示 $1$ 个或更多数字。 +- 注意这里的正则表达式是“贪婪”的(“重复”会尽可能匹配)。如 `[0-9]?1` 将不会匹配 `1` (因为 `[0-9]?` 将 `1` 匹配上,导致模板串剩余的那个 `1` 无法匹配)。 ## 使用项别名 -推荐给 `readInt/readInteger/readLong/readDouble/readWord/readToken/readString/readLine` 等的有限制调用最后多传入一个 `string` 参数,即当前读入的项的别名,使报错易读。例如使用 `inf.readInt(1, 100, "n")` 而非 `inf.readInt(1, 100)`,报错信息将为 `FAIL Integer parameter [name=n] equals to 0, violates the range [1, 100]`。 +推荐给 `readInt/readInteger/readLong/readDouble/readWord/readToken/readString/readLine` 等的有限制调用最后多传入一个 `string` 参数,即当前读入的项的别名,使报错易读。例如使用 `inf.readInt(1, 100, "n")` 而非 `inf.readInt(1, 100)` ,报错信息将为 `FAIL Integer parameter [name=n] equals to 0, violates the range [1, 100]` 。 -## 使用 `ensure/ensuref()` +## 使用 `ensure/ensuref()` -这两个函数用于检查条件是否成立(类似于 `assert()`)。例如检查 $x_i \neq y_i$,我们可以使用 +这两个函数用于检查条件是否成立(类似于 `assert()` )。例如检查 $x_i \neq y_i$ ,我们可以使用 ```cpp ensuref(x_i != y_i, "Graph can't contain loops"); @@ -91,7 +91,7 @@ ensuref(s.length() % 2 == 0, int(s.length())); ``` -方便地,我们可以使用 `ensure(x> y)`,如果条件不满足报错将为 `FAIL Condition failed: "x > y"`。 +方便地,我们可以使用 `ensure(x> y)` ,如果条件不满足报错将为 `FAIL Condition failed: "x > y"` 。 ???+ warning 注意全局与成员 `ensure/ensuref()` 的区别 @@ -100,4 +100,4 @@ ensuref(s.length() % 2 == 0, 成员函数 `InStream::ensure/ensuref()` 一般用于判断选手和参考程序的输出是否合法。当 `Stream` 为 `ouf` 时,返回 `_wa`;为 `inf`(一般不使用)或 `ans` 时,返回 `_fail`。详见 [Checker - 编写 readAns 函数](/intro/testlib/checker/#_3)。 -**本文翻译并综合自[Testlib - Codeforces](https://codeforces.com/testlib)系列。`testlib.h` 的 GitHub 存储库为[MikeMirzayanov/testlib](https://github.com/MikeMirzayanov/testlib)。** +**本文翻译并综合自[Testlib - Codeforces](https://codeforces.com/testlib)系列。 `testlib.h` 的 GitHub 存储库为[MikeMirzayanov/testlib](https://github.com/MikeMirzayanov/testlib)。** diff --git a/docs/intro/testlib/generator.md b/docs/intro/testlib/generator.md index acb05fdd..987ac237 100644 --- a/docs/intro/testlib/generator.md +++ b/docs/intro/testlib/generator.md @@ -20,28 +20,28 @@ int main(int argc, char* argv[]) { ## 为什么要使用 Testlib? -有人说写 generator 不需要用 Teslib,它在这没什么用。实际上这是个不正确的想法。一个好的 generator 应该满足这一点:**在任何环境下对于相同输入它给出相同输出**。写 generator 就避免不了生成随机值,平时我们用的 `rand()` 或 C++11 的 `mt19937/uniform_int_distribution`,当操作系统不同、使用不同编译器编译、不同时间运行等,它们的输出都可能不同(对于非常常用的 `srand(time(0))`,这是显然的),而这就会给生成数据带来不确定性。 +有人说写 generator 不需要用 Teslib,它在这没什么用。实际上这是个不正确的想法。一个好的 generator 应该满足这一点:**在任何环境下对于相同输入它给出相同输出**。写 generator 就避免不了生成随机值,平时我们用的 `rand()` 或 C++11 的 `mt19937/uniform_int_distribution` ,当操作系统不同、使用不同编译器编译、不同时间运行等,它们的输出都可能不同(对于非常常用的 `srand(time(0))` ,这是显然的),而这就会给生成数据带来不确定性。 -而 Testlib 中的随机值生成函数则保证了相同调用会输出相同值,与 generator 本身或平台均无关。另外。它给生成各种要求的随机值提供了很大便利,如 `rnd.next("[a-z]{1,10}")` 会生成一个长度在 $[1,10]$ 范围内的串,每个字符为 `a` 到 `z`,很方便吧! +而 Testlib 中的随机值生成函数则保证了相同调用会输出相同值,与 generator 本身或平台均无关。另外。它给生成各种要求的随机值提供了很大便利,如 `rnd.next("[a-z]{1,10}")` 会生成一个长度在 $[1,10]$ 范围内的串,每个字符为 `a` 到 `z` ,很方便吧! ## Testlib 能做什么? -在一切之前,先执行 `registerGen(argc, argv, 1)` 初始化 Testlib(其中 `1` 是使用的 generator 版本,通常保持不变),然后我们就可以使用 `rnd` 对象来生成随机值。随机数种子取自命令行参数的哈希值,对于某 generator `g.cpp`,`g 100` (Unix-Like) 和 `g.exe "100"` (Windows) 将会有相同的输出,而 `g 100 0` 则与它们不同。 +在一切之前,先执行 `registerGen(argc, argv, 1)` 初始化 Testlib(其中 `1` 是使用的 generator 版本,通常保持不变),然后我们就可以使用 `rnd` 对象来生成随机值。随机数种子取自命令行参数的哈希值,对于某 generator `g.cpp` , `g 100` (Unix-Like) 和 `g.exe "100"` (Windows) 将会有相同的输出,而 `g 100 0` 则与它们不同。 -`rnd` 对象的类型为 `random_t`,你可以建立一个新的随机值生成对象,不过通常你不需要这么做。 + `rnd` 对象的类型为 `random_t` ,你可以建立一个新的随机值生成对象,不过通常你不需要这么做。 该对象有许多有用的成员函数,下面是一些例子: -| 调用 | 含义 | -| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `rnd.next(4)` | 等概率生成一个 $[0,4)$ 范围内的整数 | -| `rnd.next(4, 100)` | 等概率生成一个 $[4,100]$ 范围内的整数 | -| `rnd.next(10.0)` | 等概率生成一个 $[0,10.0)$ 范围内的浮点数 | -| `rnd.next("one | two | three")` | 等概率从 `one`, `two`, `three` 三个串中返回一个 | -| `rnd.wnext(4, t)` | `wnext()` 是一个生成不等分布(具有偏移期望)的函数,$t$ 表示调用 `next()` 的次数,并取生成值的最大值。例如 `rnd.wnext(3, 1)` 等同于 `max({rnd.next(3), rnd.next(3)})`;`rnd.wnext(4, 2)` 等同于 `max({rnd.next(4), rnd.next(4), rnd.next(4)})`。如果 $t<0$,则为调用 $-t$ 次,取最小值;如果 $t=0$,等同于 `next()`。 | -| `rnd.any(container)` | 等概率返回一个具有随机访问迭代器(如 `std::vector` 和 `std::string`)的容器内的某一元素的引用 | +| 调用 | 含义 | +| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `rnd.next(4)` | 等概率生成一个 $[0,4)$ 范围内的整数 | +| `rnd.next(4, 100)` | 等概率生成一个 $[4,100]$ 范围内的整数 | +| `rnd.next(10.0)` | 等概率生成一个 $[0,10.0)$ 范围内的浮点数 | +| `rnd.next("one | two | three")` | 等概率从 `one` , `two` , `three` 三个串中返回一个 | +| `rnd.wnext(4, t)` | `wnext()` 是一个生成不等分布(具有偏移期望)的函数, $t$ 表示调用 `next()` 的次数,并取生成值的最大值。例如 `rnd.wnext(3, 1)` 等同于 `max({rnd.next(3), rnd.next(3)})` ; `rnd.wnext(4, 2)` 等同于 `max({rnd.next(4), rnd.next(4), rnd.next(4)})` 。如果 $t<0$ ,则为调用 $-t$ 次,取最小值;如果 $t=0$ ,等同于 `next()` 。 | +| `rnd.any(container)` | 等概率返回一个具有随机访问迭代器(如 `std::vector` 和 `std::string` )的容器内的某一元素的引用 | -另外,不要使用 `std::random_shuffle()`,请使用 Testlib 中的 `shuffle()`,它同样接受一对迭代器。它使用 `rnd` 来打乱序列,即满足如上 “好的 generator” 的要求。 +另外,不要使用 `std::random_shuffle()` ,请使用 Testlib 中的 `shuffle()` ,它同样接受一对迭代器。它使用 `rnd` 来打乱序列,即满足如上“好的 generator”的要求。 极简正则表达式:[通用](./general.md) @@ -86,16 +86,16 @@ for (int i = 0; i + 1 < n; i++) ## 一次性生成多组数据 -跟不使用 Testlib 编写的时候一样,每次输出前重定向输出流就好,不过 Testlib 提供了一个辅助函数 `startTest(test_index)`,它帮助你将输出流重定向到 `test_index` 文件。 +跟不使用 Testlib 编写的时候一样,每次输出前重定向输出流就好,不过 Testlib 提供了一个辅助函数 `startTest(test_index)` ,它帮助你将输出流重定向到 `test_index` 文件。 ## 一些注意事项 - 严格遵循题目的格式要求,如空格和换行,注意文件的末尾应有一个换行。 -- 对于大数据首选 `printf` 而非 `cout`,以提高性能。(不建议在使用 Testlib 时关闭流同步) -- 不使用 UB(Undefined Behavior,未定义行为),如本文开头的那个示例,输出如果写成 `cout < NOI 竞赛的环境要求 或许大家对自己 Windows 环境下的 Dev-C++ 等都已熟识,但是当场景突然切换到 Linux 的时候,你会不会不知所措? -> 「想用 Ctrl+C 复制,结果退出了程序」 +> 「想用Ctrl+C复制,结果退出了程序」 > 「平时 AC 的程序模板到了 Linux 上就 WA」…… -![平台差异(转自百度文库”NOIP 标准评测系统及相关问题 “)](./images/WSL3.png) +![平台差异(转自百度文库”NOIP 标准评测系统及相关问题“)](./images/WSL3.png)
平台差异(转自百度文库“NOIP 标准评测系统及相关问题”)
@@ -23,11 +23,11 @@ 虽然在 NOI 的官网已经放出了 NOI Linux 的 ISO 镜像,但是如果跑虚拟机的话,配置也相当麻烦,包括激活 VMware,用 VMware 装系统开虚拟机等步骤,且 NOI Linux 默认自带图形界面,两个系统一起运行是低配党的噩梦。 Windows 10 作为微软的新一代操作系统,紧跟时代潮流,在一周年更新时推出了 Linux 子系统(WSL),可以供装不起 VMware 等虚拟机的同学食用。 -缺点是没有 NOI 评测用的 **Arbiter**,但是在各大 OJ 背书的情况下谁在乎呢…… -???+ note "补充资料:何为 Linux 子系统(WSL)?(via 百度百科)" +缺点是没有 NOI 评测用的**Arbiter**,但是在各大 OJ 背书的情况下谁在乎呢…… +???+ note "补充资料:何为 Linux 子系统(WSL)?(via 百度百科)" Windows Subsystem for Linux(简称 WSL)是一个为在 Windows 10 上能够原生运行 Linux 二进制可执行文件(ELF 格式)的兼容层。它是由微软与 Canonical 公司合作开发,目标是使纯正的 Ubuntu, OpenSUSE, Kali Linux 和 Debian 映像能下载和解压到用户的本地计算机,并且映像内的工具和实用工具能在此子系统上原生运行。 - WSL 提供了一个微软开发的 Linux 兼容内核接口(不包含 Linux 代码),来自 Linux 的用户模式二进制文件在其上运行。 - 此子系统起源于命运多舛的 Astoria 项目,其目的是允许 Android 应用运行在 Windows 10 Mobile 上。此功能组件从 Windows 10 Insider Preview build 14316 开始可用。 +WSL 提供了一个微软开发的 Linux 兼容内核接口(不包含 Linux 代码),来自 Linux 的用户模式二进制文件在其上运行。 +此子系统起源于命运多舛的 Astoria 项目,其目的是允许 Android 应用运行在 Windows 10 Mobile 上。此功能组件从 Windows 10 Insider Preview build 14316 开始可用。 * * * @@ -36,8 +36,7 @@ Windows 10 作为微软的新一代操作系统,紧跟时代潮流,在一周 首先,你需要一个最新的 Windows 10 操作系统,这点不必多说。 其次,你需要配置一下开发人员模式环境。 -1. 设置 -> 更新与安全 -> 开发人员模式框选 -> 是 - ![来,跟着箭头走](./images/WSL4.png) +1. 设置 -> 更新与安全 -> 开发人员模式框选 -> 是![来,跟着箭头走](./images/WSL4.png)
来,跟着箭头走
@@ -50,35 +49,33 @@ Windows 10 作为微软的新一代操作系统,紧跟时代潮流,在一周 ## 开搞 去 Windows 自带的应用商店,搜索 "Ubuntu",然后选第一个安装。 -亦可打开 +亦可打开 ???+ warning Windows 10 商店的第一个 Ubuntu 随着 Ubuntu 的更新而更新,因此内容可能会有所改变。 可使用 `sudo lsb_release -a` 查看自己的 Ubuntu 版本。 也可安装带有版本号的旧 Linux 版本(如本次演示使用了**16.04**)。 安装完后,打开 Ubuntu,等待一段时间,让其自己配置,不久就会提示你设置用户名和密码。 -(这里看你喜好,推荐设置短点,毕竟 ** 本地环境不怕攻击 **) +(这里看你喜好,推荐设置短点,毕竟**本地环境不怕攻击**) **Linux 区分大小写!** -![](./images/WSL6.png) - 这样之后,一个纯净的 Ubuntu 系统安装完成了! +![](./images/WSL6.png)这样之后,一个纯净的 Ubuntu 系统安装完成了! ## 基础配置 - ** 以下命令均可直接右键复制粘贴进窗口哦!** +**以下命令均可直接右键复制粘贴进窗口哦!** -![](./images/WSL7.png) - 正如图片所示,这个系统纯净到连个编译器都没有,所以这一节来看看基础的环境配置。 +![](./images/WSL7.png)正如图片所示,这个系统纯净到连个编译器都没有,所以这一节来看看基础的环境配置。 ### 更换为国内软件源 -Ubuntu 默认的软件源在国外,我们可以换为国内的加快速度,如 [清华 TUNA 的软件源](https://mirrors.tuna.tsinghua.edu.cn/help/ubuntu/)。 +Ubuntu 默认的软件源在国外,我们可以换为国内的加快速度,如[清华 TUNA 的软件源](https://mirrors.tuna.tsinghua.edu.cn/help/ubuntu/)。 -可以访问 [TUNA 的页面](https://mirrors.tuna.tsinghua.edu.cn/help/ubuntu/) 来获得国内源的信息。 +可以访问[TUNA 的页面](https://mirrors.tuna.tsinghua.edu.cn/help/ubuntu/)来获得国内源的信息。 ???+ warning - ** 请在页面中寻找与自己系统版本相配的源(可使用 `sudo lsb_release -a` 查看,具体详见 `0x03` ) - ** 除非你知道你在做什么,否则不要使用与自己的系统版本不匹配的源!\*\* +**请在页面中寻找与自己系统版本相配的源(可使用 `sudo lsb_release -a` 查看,具体详见 `0x03` ) +**除非你知道你在做什么,否则不要使用与自己的系统版本不匹配的源!\*\* 使用的命令 @@ -108,9 +105,7 @@ sudo dpkg-reconfigure locales 使用 `sudo dpkg-reconfigure locales` 进入菜单,按空格选择带 `zh_CN` 的选项,选完后回车,下一个菜单中选 `zh_CN.UTF-8` 打回车。 -![](./images/WSL10.png) -![](./images/WSL11.png) - 之后关上 Ubuntu 重开一遍登录,是不是变中文了? +![](./images/WSL10.png)![](./images/WSL11.png)之后关上 Ubuntu 重开一遍登录,是不是变中文了? 再依次输入下列命令,把 `man` 帮助页替换为中文:[via](https://blog.csdn.net/qq_14989227/article/details/72954523) ```bash @@ -120,7 +115,7 @@ sudo vi /etc/manpath.config :wq ``` - 可以用 `man help` 测试下。 +可以用 `man help` 测试下。 ### 安装编译环境 @@ -133,7 +128,7 @@ chmod +x install.sh && ./install.sh ``` 这是基础的 + NOI 官方要求环境,如有需要可以用 `apt install 程序名` 来安装别的。 -若想安装其他版本可以参考下 [这个](https://www.cnblogs.com/EasonJim/p/7144017.html) +若想安装其他版本可以参考下[这个](https://www.cnblogs.com/EasonJim/p/7144017.html) 来个程序玩玩: @@ -144,7 +139,7 @@ $ ./cpuid AMD Ryzen 5 1400 Quad-Core Processor ``` -**Tips:Linux 环境下可执行文件可不带扩展名,实现方式看上方命令行 ** +**Tips:Linux 环境下可执行文件可不带扩展名,实现方式看上方命令行** ## 进阶操作 @@ -167,8 +162,7 @@ echo "xfce4-session" >~/.xsession sudo service xrdp restart ``` - 为了防止和你计算机本来带的远程桌面冲突,最好换一下端口。 -![](./images/WSL12.png) +为了防止和你计算机本来带的远程桌面冲突,最好换一下端口。![](./images/WSL12.png)
不换端口的结果
运行命令 `vim /etc/xrdp/xrdp.ini`,把 `port=3389` 改为别的(如 `port=3390`),然后保存即可。 @@ -183,7 +177,7 @@ sudo service xrdp restart ##### 客户端:安装 Xterm - 我们进入 Ubuntu 环境,安装 xterm: +我们进入 Ubuntu 环境,安装 xterm: ```bash sudo apt-get install xterm -y @@ -191,11 +185,11 @@ sudo apt-get install xterm -y ##### 服务端:下载 Xming Server - 去 下载最新的 Xming Server,然后一路安装: +去下载最新的 Xming Server,然后一路安装: ![](./images/WSL16.png) - 如果你把 Launch Xming 框点掉了,记得去开始菜单再打开: +如果你把 Launch Xming 框点掉了,记得去开始菜单再打开: ![别忘了!](./images/WSL17.png) - 之后再回到 Ubuntu,键入如下指令: +之后再回到 Ubuntu,键入如下指令: ```bash DISPLAY=:0 xterm @@ -203,15 +197,15 @@ DISPLAY=:0 xterm **Duang!** ![](./images/WSL18.png) -不过貌似只支持命令行…… 这时上一种方法的优势就显而易见了 - 如果你和我一样使用了 xfce4,在弹出的窗口中使用如下命令激活 xfce4: +不过貌似只支持命令行……这时上一种方法的优势就显而易见了 +如果你和我一样使用了 xfce4,在弹出的窗口中使用如下命令激活 xfce4: ```bash xfce4-session ``` ![](./images/WSL19.png) - 不过这是什么效果......**(在 Xming 中使用 ** Ctrl + C ** 就可以退出这个鬼畜界面)** +不过这是什么效果……**(在 Xming 中使用**Ctrl+C**就可以退出这个鬼畜界面)** ![](./images/WSL20.png)
达成成就:Windows+Linux 二合一
@@ -220,10 +214,10 @@ xfce4-session #### 与 Windows 内原硬盘分区交互 -硬盘分区作为文件夹在 `/mnt/` 里放着,因此可以直接交互,比如说直接编译个二进制文件,或者往 Ubuntu 里传文件什么的...... - 具体演示: +硬盘分区作为文件夹在 `/mnt/` 里放着,因此可以直接交互,比如说直接编译个二进制文件,或者往 Ubuntu 里传文件什么的…… +具体演示: ![](./images/WSL22.png) -![](./images/WSL23.png) +![](./images/WSL23.png)
这里也可以建立一些 Windows(一般情况下)建不了的文件,例如带点文件夹
** 乱码是因为我用的预览体验系统……不过用正式版也可以了!** @@ -232,8 +226,8 @@ xfce4-session - 如何在子系统下进行 xxx? 该怎么用怎么用,可以用自带命令行,实在不行参考教程唤醒图形界面。 - 比如说 vim,在命令行中键入 `man vim`,会给出一份详尽的使用方法。 - 亦可使用 `vim --help`。 + 比如说 vim,在命令行中键入 `man vim` ,会给出一份详尽的使用方法。 + 亦可使用 `vim --help` 。 - 占用量大? Sorry,这个系统和 Windows 10 共用 Host,所以理论上是比虚拟机占用小的。 而且只要别装太多应用,应该还是可以带动的。 @@ -244,16 +238,16 @@ xfce4-session 这里列举了文中提到的链接,以便查阅。 -1. [NOIP 标准评测系统及相关问题, smart0326, 2014-05-19, 百度文库](https://wenku.baidu.com/view/8246d96cdd36a32d72758143.html) -2. [WSL, 百度百科](https://baike.baidu.com/item/wsl/20359185) -3. [Run Bash on Ubuntu on Windows, Mike Harsh, 2016-05-30, Windows Blog](https://blogs.windows.com/buildingapps/2016/03/30/run-bash-on-ubuntu-on-windows/#cie8WdR3uSjgR5Ru.97) -4. [Windows Subsystem for Linux Documentation, MSDN](https://docs.microsoft.com/zh-cn/windows/wsl/about) -5. [NOI 系列活动标准竞赛环境, 2016-11-08, NOI 官网](http://www.noi.cn/2016-11-08-03-42-01) -6. [购买 Ubuntu, Microsoft Store](https://www.microsoft.com/zh-cn/p/ubuntu/9nblggh4msv6) -7. [Ubuntu 镜像使用帮助, 清华 TUNA](https://mirrors.tuna.tsinghua.edu.cn/help/ubuntu/) -8. [Ubuntu 的 man 命令帮助如何设置中文版, Frank 看庐山, 2017-06-09](https://blog.csdn.net/qq_14989227/article/details/72954523) -9. [Xming X Server for Windows, SourceForge](https://sourceforge.net/projects/xming/) -10. [Sudo, Wikipedia](https://zh.wikipedia.org/wiki/Sudo) +1. [NOIP 标准评测系统及相关问题,smart0326, 2014-05-19, 百度文库](https://wenku.baidu.com/view/8246d96cdd36a32d72758143.html) +2. [WSL, 百度百科](https://baike.baidu.com/item/wsl/20359185) +3. [Run Bash on Ubuntu on Windows, Mike Harsh, 2016-05-30, Windows Blog](https://blogs.windows.com/buildingapps/2016/03/30/run-bash-on-ubuntu-on-windows/#cie8WdR3uSjgR5Ru.97) +4. [Windows Subsystem for Linux Documentation, MSDN](https://docs.microsoft.com/zh-cn/windows/wsl/about) +5. [NOI 系列活动标准竞赛环境,2016-11-08, NOI 官网](http://www.noi.cn/2016-11-08-03-42-01) +6. [购买 Ubuntu, Microsoft Store](https://www.microsoft.com/zh-cn/p/ubuntu/9nblggh4msv6) +7. [Ubuntu 镜像使用帮助,清华 TUNA](https://mirrors.tuna.tsinghua.edu.cn/help/ubuntu/) +8. [Ubuntu 的 man 命令帮助如何设置中文版,Frank 看庐山,2017-06-09](https://blog.csdn.net/qq_14989227/article/details/72954523) +9. [Xming X Server for Windows, SourceForge](https://sourceforge.net/projects/xming/) +10. [Sudo, Wikipedia](https://zh.wikipedia.org/wiki/Sudo) ## 延伸内容 @@ -261,4 +255,4 @@ xfce4-session ### 后记 -本文最初发布于 [洛谷日报 #6](https://www.luogu.org/discuss/show/48491),现由原作者搬运至此,有删改。 +本文最初发布于[洛谷日报 #6](https://www.luogu.org/discuss/show/48491),现由原作者搬运至此,有删改。 diff --git a/docs/math/base.md b/docs/math/base.md index e83a363e..fc8f733e 100644 --- a/docs/math/base.md +++ b/docs/math/base.md @@ -10,7 +10,7 @@ 在八进制下,有 $0,1,2,3,4,5,6,7$ 八个数字。 -一般情况下,八进制数以 `oxx`(其中 `o` 为八进制的前缀,`xx` 代表八进制数)的形式来表示。 +一般情况下,八进制数以 `oxx` (其中 `o` 为八进制的前缀, `xx` 代表八进制数)的形式来表示。 ## 十六进制 @@ -18,15 +18,15 @@ 十六进制与二进制相比,最大的优点就是表示的数字长度较短,一位十六进制数可以表示 4 位二进制数。 -一般情况下,十六进制数以 `0xdbf`(其中 `0x` 为十六进制数的前缀)的形式来表示。 +一般情况下,十六进制数以 `0xdbf` (其中 `0x` 为十六进制数的前缀)的形式来表示。 ## 进制间的相互转化 -### 十进制转二进制 / 八进制 / 十六进制 +### 十进制转二进制/八进制/十六进制 这里以二进制为例来演示,其他进制的原理与其类似。 -整数部分,把十进制数不断执行除 2 操作,直至商数为 0。读余数从下读到上,即是二进制的整数部分数字。 小数部分,则用其乘 2,取其整数部分的结果,再用计算后的小数部分依此重复计算,算到小数部分全为 0 为止,之后从上到下,读所有计算后整数部分的数字,即为二进制的小数部分数字。 +整数部分,把十进制数不断执行除 2 操作,直至商数为 0。读余数从下读到上,即是二进制的整数部分数字。小数部分,则用其乘 2,取其整数部分的结果,再用计算后的小数部分依此重复计算,算到小数部分全为 0 为止,之后从上到下,读所有计算后整数部分的数字,即为二进制的小数部分数字。 ```text 将33.25转化为二进制数 @@ -42,9 +42,9 @@ 0.5*2=1 1 ``` -即 $33.25 = (100001.01)_2$ +即 $33.25 = (100001.01)_2$ -### 二进制 / 八进制 / 十六进制转十进制 +### 二进制/八进制/十六进制转十进制 还是以二进制为例。 @@ -56,8 +56,8 @@ =26.25 ``` -即 $(11010)_2 = (26.25)_{10}$ +即 $(11010)_2 = (26.25)_{10}$ -### 二进制 / 八进制 / 十六进制间的相互转换 +### 二进制/八进制/十六进制间的相互转换 -一个八进制位可以用 3 个二进制位来表示(因为 $2^3 =8$ ), 一个十六进制位可以用 4 个二进制位来表示( $2^4 = 16$ ),反之同理。 +一个八进制位可以用 3 个二进制位来表示(因为 $2^3 =8$ ),一个十六进制位可以用 4 个二进制位来表示( $2^4 = 16$ ),反之同理。 diff --git a/docs/math/bezouts.md b/docs/math/bezouts.md index 126f150a..60cf319c 100644 --- a/docs/math/bezouts.md +++ b/docs/math/bezouts.md @@ -4,27 +4,27 @@ 其内容是: -设 $a,b$ 是不全为零的整数, 则存在整数 $x,y$, 使得 $ax+by=\gcd(a,b)$. +设 $a,b$ 是不全为零的整数,则存在整数 $x,y$ , 使得 $ax+by=\gcd(a,b)$ . ## 证明 -1. 若任何一个等于 $0$, 则 $\gcd(a,b)=a$. 这时定理显然成立. +1. 若任何一个等于 $0$ , 则 $\gcd(a,b)=a$ . 这时定理显然成立。 -2. 若 $a,b$ 不等于 $0$. +2. 若 $a,b$ 不等于 $0$ . - 由于 $\gcd(a,b)=\gcd(a,-b)$, + 由于 $\gcd(a,b)=\gcd(a,-b)$ , - 不妨设 $a,b$ 都大于 $0$, $a\geq b,\gcd(a,b)=d$. + 不妨设 $a,b$ 都大于 $0$ , $a\geq b,\gcd(a,b)=d$ . - 对 $ax+by=d$, 两边同时除以$d$, 可得 $a_1x+b_1y=1$, 其中 $(a_1,b_1)=1$. + 对 $ax+by=d$ , 两边同时除以 $d$ , 可得 $a_1x+b_1y=1$ , 其中 $(a_1,b_1)=1$ . - 转证 $a_1x+b_1y=1$. 由带余除法: + 转证 $a_1x+b_1y=1$ . 由带余除法: $$ \begin{aligned}a_1 &= q_1b+r_1 &(0\leq r_1= 1` 而不是 `i >= 0` 是因为当整个数字等于 $0$ 时仍希望输出一个字符 `0`。 +输出也按照存储的逆序输出。由于不希望输出前导零,故这里从最高位开始向下寻找第一个非零位,从此处开始输出;终止条件 `i >= 1` 而不是 `i >= 0` 是因为当整个数字等于 $0$ 时仍希望输出一个字符 `0` 。 ```cpp void print(int a[LEN]) { @@ -64,7 +64,7 @@ void print(int a[LEN]) { 拼起来就是一个完整的复读机程序咯。 -??? "`copycat.cpp`" +??? " `copycat.cpp` " ```cpp #include @@ -106,7 +106,7 @@ void print(int a[LEN]) { ## 四则运算 -四则运算中难度也各不相同。最简单的是高精度加减法,其次是高精度—单精度(普通的 `int`)乘法和高精度—高精度乘法,最后是高精度—高精度除法。 +四则运算中难度也各不相同。最简单的是高精度加减法,其次是高精度—单精度(普通的 `int` )乘法和高精度—高精度乘法,最后是高精度—高精度除法。 我们将按这个顺序分别实现所有要求的功能。 @@ -116,7 +116,7 @@ void print(int a[LEN]) { ![](./images/plus.png) -也就是从最低位开始,将两个加数对应位置上的数码相加,并判断是否达到或超过 $10$。如果达到,那么处理进位:将更高一位的结果上增加 $1$,当前位的结果减少 $10$。 +也就是从最低位开始,将两个加数对应位置上的数码相加,并判断是否达到或超过 $10$ 。如果达到,那么处理进位:将更高一位的结果上增加 $1$ ,当前位的结果减少 $10$ 。 ```cpp void add(int a[LEN], int b[LEN], int c[LEN]) { @@ -139,7 +139,7 @@ void add(int a[LEN], int b[LEN], int c[LEN]) { 试着和上一部分结合,可以得到一个加法计算器。 -??? "`adder.cpp`" +??? " `adder.cpp` " ```cpp #include @@ -200,7 +200,7 @@ void add(int a[LEN], int b[LEN], int c[LEN]) { ![](./images/subtraction.png) -从个位起逐位相减,遇到负的情况则向上一位借 $1$。整体思路与加法完全一致。 +从个位起逐位相减,遇到负的情况则向上一位借 $1$ 。整体思路与加法完全一致。 ```cpp void sub(int a[LEN], int b[LEN], int c[LEN]) { @@ -218,9 +218,9 @@ void sub(int a[LEN], int b[LEN], int c[LEN]) { } ``` -将上一个程序中的 `add()` 替换成 `sub()`,就有了一个减法计算器。 +将上一个程序中的 `add()` 替换成 `sub()` ,就有了一个减法计算器。 -??? "`subtractor.cpp`" +??? " `subtractor.cpp` " ```cpp #include @@ -280,25 +280,25 @@ void sub(int a[LEN], int b[LEN], int c[LEN]) { } ``` -试一试,输入 `1 2` —— 输出 `/9999999`,诶这个 OI Wiki 怎么给了我一份假的代码啊…… +试一试,输入 `1 2` ——输出 `/9999999` ,诶这个 OI Wiki 怎么给了我一份假的代码啊…… -如你所见,上面的程序只能处理 $a \geq b$ 的情况,至于负数如何处理 —— 就交给聪明的你啦。 +如你所见,上面的程序只能处理 $a \geq b$ 的情况,至于负数如何处理——就交给聪明的你啦。 ### 乘法 #### 高精度—单精度 -高精度乘法,也就是竖…… 等会儿等会儿! +高精度乘法,也就是竖……等会儿等会儿! 先考虑一个简单的情况:乘数中的一个是普通的 `int` 类型。有没有简单的处理方法呢? -一个直观的思路是直接将 $a$ 每一位上的数字乘以 $b$。从数值上来说,这个方法是正确的,但它并不符合十进制表示法,因此需要将它重新整理成正常的样子。 +一个直观的思路是直接将 $a$ 每一位上的数字乘以 $b$ 。从数值上来说,这个方法是正确的,但它并不符合十进制表示法,因此需要将它重新整理成正常的样子。 -重整的方式,也是从个位开始逐位向上处理进位。但是这里的进位可能非常大,甚至远大于 $9$,因为每一位被乘上之后都可能达到 $9b$ 的数量级。所以这里的进位不能再简单地进行 $-10$ 运算,而是要通过除以 $10$ 的商以及余数计算。详见代码注释,也可以参考下图展示的一个计算高精度数 $1337$ 乘以单精度数 $42$ 的过程。 +重整的方式,也是从个位开始逐位向上处理进位。但是这里的进位可能非常大,甚至远大于 $9$ ,因为每一位被乘上之后都可能达到 $9b$ 的数量级。所以这里的进位不能再简单地进行 $-10$ 运算,而是要通过除以 $10$ 的商以及余数计算。详见代码注释,也可以参考下图展示的一个计算高精度数 $1337$ 乘以单精度数 $42$ 的过程。 ![](./images/multiplication-short.png) -当然,也是出于这个原因,这个方法需要特别关注乘数 $b$ 的范围。若它和 $10^9$(或相应整型的取值上界)属于同一数量级,那么需要慎用高精度—单精度乘法。 +当然,也是出于这个原因,这个方法需要特别关注乘数 $b$ 的范围。若它和 $10^9$ (或相应整型的取值上界)属于同一数量级,那么需要慎用高精度—单精度乘法。 ```cpp void mul_short(int a[LEN], int b, int c[LEN]) { @@ -323,7 +323,7 @@ void mul_short(int a[LEN], int b, int c[LEN]) { 如果两个乘数都是高精度,那么竖式乘法又可以大显身手了。 -回想竖式乘法的每一步,实际上是计算了若干 $a \times b_i \times 10^i$ 的和。例如计算 $1337 \times 42$,计算的就是 $1337 \times 2 \times 10^0 + 1337 \times 4 \times 10^1$。 +回想竖式乘法的每一步,实际上是计算了若干 $a \times b_i \times 10^i$ 的和。例如计算 $1337 \times 42$ ,计算的就是 $1337 \times 2 \times 10^0 + 1337 \times 4 \times 10^1$ 。 于是可以将 $b$ 分解为它的所有数码,其中每个数码都是单精度数,将它们分别与 $a$ 相乘,再向左移动到各自的位置上相加即得答案。当然,最后也需要用与上例相同的方式处理进位。 @@ -355,11 +355,11 @@ void mul(int a[LEN], int b[LEN], int c[LEN]) { ![](./images/division.png) -竖式长除法实际上可以看作一个逐次减法的过程。例如上图中商数十位的计算可以这样理解:将 $45$ 减去三次 $12$ 后变得小于 $12$,不能再减,故此位为 $3$。 +竖式长除法实际上可以看作一个逐次减法的过程。例如上图中商数十位的计算可以这样理解:将 $45$ 减去三次 $12$ 后变得小于 $12$ ,不能再减,故此位为 $3$ 。 -为了减少冗余运算,我们提前得到被除数的长度 $l_a$ 与除数的长度 $l_b$,从下标 $l_a - l_b$ 开始,从高位到低位来计算商。这和手工计算时将第一次乘法的最高位与被除数最高位对齐的做法是一样的。 +为了减少冗余运算,我们提前得到被除数的长度 $l_a$ 与除数的长度 $l_b$ ,从下标 $l_a - l_b$ 开始,从高位到低位来计算商。这和手工计算时将第一次乘法的最高位与被除数最高位对齐的做法是一样的。 -参考程序实现了一个函数 `greater_eq()` 用于判断被除数以下标 `last_dg` 为最低位,是否可以再减去除数而保持非负。此后对于商的每一位,不断调用 `greater_eq()`,并在成立的时候用高精度减法从余数中减去除数,也即模拟了竖式除法的过程。 +参考程序实现了一个函数 `greater_eq()` 用于判断被除数以下标 `last_dg` 为最低位,是否可以再减去除数而保持非负。此后对于商的每一位,不断调用 `greater_eq()` ,并在成立的时候用高精度减法从余数中减去除数,也即模拟了竖式除法的过程。 ```cpp // 被除数 a 以下标 last_dg 为最低位,是否可以再减去除数 b 而保持非负 @@ -417,7 +417,7 @@ void div(int a[LEN], int b[LEN], int c[LEN], int d[LEN]) { 将上面介绍的四则运算的实现结合,即可完成开头提到的计算器程序。 -??? "`calculator.cpp`" +??? " `calculator.cpp` " ```cpp #include @@ -560,7 +560,7 @@ void div(int a[LEN], int b[LEN], int c[LEN], int d[LEN]) { ## 封装类 -[这里](https://paste.ubuntu.com/p/7VKYzpC7dn/) 有一个封装好的高精度整数类。 +[这里](https://paste.ubuntu.com/p/7VKYzpC7dn/)有一个封装好的高精度整数类。 ??? 这里是另一个模板 diff --git a/docs/math/bit.md b/docs/math/bit.md index e71543ed..91d35380 100644 --- a/docs/math/bit.md +++ b/docs/math/bit.md @@ -1,10 +1,10 @@ 位运算就是把整数转换为二进制后,每位进行相应的运算得到结果。 -常用的运算符共 6 种,分别为与(`&`)、或(`|`)、异或(`^`)、取反(`~`)、左移(`<<`) 和右移(`>>`)。 +常用的运算符共 6 种,分别为与( `&` )、或( `|` )、异或( `^` )、取反( `~` )、左移( `<<` )和右移( `>>` )。 ## 与、或、异或 -与(`&`)或(`|`)和异或(`^`)这三者都是两者间的运算,因此在这里一起讲解。 +与( `&` )或( `|` )和异或( `^` )这三者都是两者间的运算,因此在这里一起讲解。 表示把两个整数分别转换为二进制后各位逐一比较。 @@ -14,7 +14,7 @@ | \| | 只要在两个(对应位数中)有一个 1 时就为 1 | | `^` | 只有两个(对应位数)不同时才为 1 | -`^` 运算的逆运算是它本身,也就是说两次异或同一个数最后结果不变,即 `(a ^ b) ^ b = a`。 + `^` 运算的逆运算是它本身,也就是说两次异或同一个数最后结果不变,即 `(a ^ b) ^ b = a` 。 > 举例: > @@ -32,9 +32,9 @@ 取反是对 1 个数 $num$ 进行的计算。 -`~` 把 $num$ 的补码中的 0 和 1 全部取反 (0 变为 1,1 变为 0)。 + `~` 把 $num$ 的补码中的 0 和 1 全部取反(0 变为 1,1 变为 0)。 -补码——正数的补码为其(二进制)本身,负数的补码是其(二进制)取反后 $+1$。 +补码——正数的补码为其(二进制)本身,负数的补码是其(二进制)取反后 $+1$ 。 > 举例: > @@ -50,13 +50,13 @@ 与前面的 4 种运算相似,这两种运算仍是把整数转换为二进制后进行操作。 -左移(`<<`) 将转化为二进制后的数字整体向左移动。 +左移( `<<` )将转化为二进制后的数字整体向左移动。 -> `num << i` // 表示将 $num$ 转换为二进制后向左移动 $i$ 位(所得的值) +> `num << i` //表示将 $num$ 转换为二进制后向左移动 $i$ 位(所得的值) -右移(`>>`) 将转化为二进制后的数字整体向右移动。 +右移( `>>` )将转化为二进制后的数字整体向右移动。 -> `num >> i` // 表示将 $num$ 转换为二进制后向左移动 $i$ 位(所得的值) +> `num >> i` //表示将 $num$ 转换为二进制后向左移动 $i$ 位(所得的值) > > 举例: > @@ -79,15 +79,15 @@ ## 位运算的应用 -如果 $num$ 是整数,`num << i` 相当于 $num \times 2^i$ ,而 `num >> i` 相当于 $num \div 2^i$ 。 (位运算比 `%` 和 `/` 操作快得多) -(据 2018JSOI 夏令营,效率可以提高 60%) +如果 $num$ 是整数, `num << i` 相当于 $num \times 2^i$ ,而 `num >> i` 相当于 $num \div 2^i$ 。(位运算比 `%` 和 `/` 操作快得多) +(据 2018JSOI 夏令营,效率可以提高 60%) !!! warning 我们平常写的除法是向 0 取整,而这里的右移是向下取整(注意这里的区别),即当数大于等于 0 时两种方法等价,当数小于 0 时会有区别,如: $-1 \div 2 = 0$ , 而 $-1 >> 1 = -1$ -`num * 10 = (num<<1) + (num<<3)` + `num * 10 = (num<<1) + (num<<3)` -`num & 1` 相当于取 $num$ 二进制的最末位,可用于判断 $num$ 的奇偶性,二进制的最末位为 0 表示该数为偶数,最末位为 1 表示该数为奇数。 + `num & 1` 相当于取 $num$ 二进制的最末位,可用于判断 $num$ 的奇偶性,二进制的最末位为 0 表示该数为偶数,最末位为 1 表示该数为奇数。 ```cpp // 利用位运算的快捷的 swap 代码 @@ -98,17 +98,17 @@ void swap(int &a, int &b) { } ``` -一个数的二进制表示可以看作是一个集合(0 表示不在集合中,1 表示在集合中)。比如集合 `{1, 3, 4, 8}`,可以表示成 `0b00000000000000000000000100011010`,十进制就是 $2^8+2^4+2^3+2^1=282$。 +一个数的二进制表示可以看作是一个集合(0 表示不在集合中,1 表示在集合中)。比如集合 `{1, 3, 4, 8}` ,可以表示成 `0b00000000000000000000000100011010` ,十进制就是 $2^8+2^4+2^3+2^1=282$ 。 而对应的位运算也就可以看作是对集合进行的操作。 -| 操作 | 集合表示 | 位运算语句 | -| --- | --------------: | :-----: | -| 交集 | $a \cap b$ | `a & b` | -| 并集 | $a \cup b$ | `a | b` | -| 补集 | $\bar{a}$ | `~a` | -| 差集 | $a \setminus b$ | `~a` | -| 对称差 | $a\triangle b$ | `a ^ b` | +| 操作 | 集合表示 | 位运算语句 | +| --- | ----------------: | :-------: | +| 交集 | $a \cap b$ | `a & b` | +| 并集 | $a \cup b$ | `a | b` | +| 补集 | $\bar{a}$ | `~a` | +| 差集 | $a \setminus b$ | `~a` | +| 对称差 | $a\triangle b$ | `a ^ b` | * * * diff --git a/docs/math/bsgs.md b/docs/math/bsgs.md index 12cf9c43..0295a65d 100644 --- a/docs/math/bsgs.md +++ b/docs/math/bsgs.md @@ -12,15 +12,15 @@ $$ 其中 $p$ 是个质数的方程的解 $x$ 满足 $0 \le x < p$ . -令 $x = A \lceil \sqrt p \rceil - B$,其中 $0\le A,B \le \lceil \sqrt p \rceil$, +令 $x = A \lceil \sqrt p \rceil - B$ ,其中 $0\le A,B \le \lceil \sqrt p \rceil$ , -则有 $a^{A\lceil \sqrt p \rceil -B} \equiv b$,稍加变换,则有 $a^{A\lceil \sqrt p \rceil} \equiv ba^B$. +则有 $a^{A\lceil \sqrt p \rceil -B} \equiv b$ ,稍加变换,则有 $a^{A\lceil \sqrt p \rceil} \equiv ba^B$ . -我们已知的是 $a,b$,所以我们可以先算出等式右边的 $ba^B$ 的所有取值,枚举 $B$,用 hash/map 存下来,然后逐一计算 $a^{A\lceil \sqrt p \rceil}$,枚举 $A$,寻找是否有与之相等的 $ba^B$,从而我们可以得到所有的 $x$,$x=A \lceil \sqrt p \rceil - B$. +我们已知的是 $a,b$ ,所以我们可以先算出等式右边的 $ba^B$ 的所有取值,枚举 $B$ ,用 hash/map 存下来,然后逐一计算 $a^{A\lceil \sqrt p \rceil}$ ,枚举 $A$ ,寻找是否有与之相等的 $ba^B$ ,从而我们可以得到所有的 $x$ , $x=A \lceil \sqrt p \rceil - B$ . -注意到 $A,B$ 均小于 $\lceil \sqrt p \rceil$,所以时间复杂度为 $O(\sqrt q)$,用 map 的话会多一个 $\log$. +注意到 $A,B$ 均小于 $\lceil \sqrt p \rceil$ ,所以时间复杂度为 $O(\sqrt q)$ ,用 map 的话会多一个 $\log$ . -[BZOJ-2480](http://www.lydsy.com/JudgeOnline/problem.php?id=2480) 是一道模板题(可能是权限题),[BZOJ-3122](http://www.lydsy.com/JudgeOnline/problem.php?id=3122) 是一道略加变化的题,代码可以在 [Steaunk 的博客](https://blog.csdn.net/Steaunk/article/details/78988376) 中看到. +[BZOJ-2480](http://www.lydsy.com/JudgeOnline/problem.php?id=2480)是一道模板题(可能是权限题),[BZOJ-3122](http://www.lydsy.com/JudgeOnline/problem.php?id=3122)是一道略加变化的题,代码可以在[Steaunk 的博客](https://blog.csdn.net/Steaunk/article/details/78988376)中看到。 ### 略微进阶篇 @@ -30,47 +30,47 @@ $$ x^a \equiv b \bmod p $$ -其中 $p$ 是个质数. +其中 $p$ 是个质数。 -该模型可以通过一系列的转化为成**基础篇**中的模型,你可能需要一些关于 [原根](/math/primitive-root/) 的概念. +该模型可以通过一系列的转化为成**基础篇**中的模型,你可能需要一些关于[原根](/math/primitive-root/)的概念。 -**原根的定义**为:对于任意数 $a$,满足 $(a,p)=1$,且 $t$ 为最小的**正整数**满足 $a^t \equiv 1 \bmod p$,则称 $t$ 是 $a$ 模 $p$ 意义下的次数,若 $t=\varphi(p)$,则称 $a$ 是 $p$ 的原根. +**原根的定义**为:对于任意数 $a$ ,满足 $(a,p)=1$ ,且 $t$ 为最小的**正整数**满足 $a^t \equiv 1 \bmod p$ ,则称 $t$ 是 $a$ 模 $p$ 意义下的次数,若 $t=\varphi(p)$ ,则称 $a$ 是 $p$ 的原根。 -首先根据**原根存在的条件**,对与所有的素数 $p>2$ 和正整数 $e$,当且仅当 $n=1,2,4,p^e,2p^e$ 时有原根, +首先根据**原根存在的条件**,对与所有的素数 $p>2$ 和正整数 $e$ ,当且仅当 $n=1,2,4,p^e,2p^e$ 时有原根, -那么由于式子中的模数 $p$ ,那么一定存在一个 $g$ 满足 $g$ 是 $p$ 的原根,即对于任意的数 $x$ 在模 $p$ 意义下一定有且仅有一个数 $i$,满足 $x = g^i$,且 $0 \le x,i < p$. +那么由于式子中的模数 $p$ ,那么一定存在一个 $g$ 满足 $g$ 是 $p$ 的原根,即对于任意的数 $x$ 在模 $p$ 意义下一定有且仅有一个数 $i$ ,满足 $x = g^i$ ,且 $0 \le x,i < p$ . -所以我们令 $x=g^c$,$g$ 是 $p$ 的原根(我们一定可以找到这个 $g$ 和 $c$),则为求 $(g^c)^a \equiv b \bmod p$ 的关于 $c$ 的解集,稍加变换,则有 $(g^a)^c \equiv b \bmod p$ ,于是就转换成了我们熟知的 **BSGS** 的基本模型了,即可在 $O(\sqrt p)$ 解决. +所以我们令 $x=g^c$ , $g$ 是 $p$ 的原根(我们一定可以找到这个 $g$ 和 $c$ ),则为求 $(g^c)^a \equiv b \bmod p$ 的关于 $c$ 的解集,稍加变换,则有 $(g^a)^c \equiv b \bmod p$ ,于是就转换成了我们熟知的**BSGS**的基本模型了,即可在 $O(\sqrt p)$ 解决。 那么关键的问题就在于如何找到这个 $g$ 了? -关于对于存在原根的数 $p$ 有这样的**性质**:若 $t$ 是 $a$ 模 $p$ 的次数(这里蕴含了 $(a,p)=1$),那么对于任意的数 $d$,满足 $a^d \equiv 1 \bmod p$,则 $t \mid d$. +关于对于存在原根的数 $p$ 有这样的**性质**:若 $t$ 是 $a$ 模 $p$ 的次数(这里蕴含了 $(a,p)=1$ ),那么对于任意的数 $d$ ,满足 $a^d \equiv 1 \bmod p$ ,则 $t \mid d$ . **PROOF** -记 $d = tq+r$,$0 \le r < t$. +记 $d = tq+r$ , $0 \le r < t$ . -$\because a^d \equiv a^{xq+r} \equiv (a^t)^qa^r \equiv a^r \equiv 1$. + $\because a^d \equiv a^{xq+r} \equiv (a^t)^qa^r \equiv a^r \equiv 1$ . -$\because 0 \le r < t$,$t$ 是 $a$ 模 $p$ 的次数,即 $t$ 是最小的**正整数**满足 $a^t \equiv 1$. + $\because 0 \le r < t$ , $t$ 是 $a$ 模 $p$ 的次数,即 $t$ 是最小的**正整数**满足 $a^t \equiv 1$ . -$\therefore r = 0$. + $\therefore r = 0$ . -即 $d = tq$,$t \mid d$ +即 $d = tq$ , $t \mid d$ **Q.E.D.** -由此当 $p$ 是质数的时候还有这样的推论:如果不存在小于 $p$ 且整除 $p-1$ 正整数 $t$, 满足 $a^t \equiv 1$,那么又根据**费马小定理**,有 $a^{p-1} \equiv 1$,所以 $p-1$ 是 $a$ 模 $p$ 的次数,即 $a$ 是 $p$ 的原根. +由此当 $p$ 是质数的时候还有这样的推论:如果不存在小于 $p$ 且整除 $p-1$ 正整数 $t$ , 满足 $a^t \equiv 1$ ,那么又根据**费马小定理**,有 $a^{p-1} \equiv 1$ ,所以 $p-1$ 是 $a$ 模 $p$ 的次数,即 $a$ 是 $p$ 的原根。 -于是可以得到一种基于**原根分布**的算法来找原根,首先把 $p-1$ 的因数全部求出来,然后从 $2$ 到 $p-1$ 枚举,判断是否为原根,如果对于数 $g$,$\exists g^t \equiv 1 \bmod p$,$t$ 是 $p-1$ 的因数,则 $g$ 一定不是 $p$ 的原根. +于是可以得到一种基于**原根分布**的算法来找原根,首先把 $p-1$ 的因数全部求出来,然后从 $2$ 到 $p-1$ 枚举,判断是否为原根,如果对于数 $g$ , $\exists g^t \equiv 1 \bmod p$ , $t$ 是 $p-1$ 的因数,则 $g$ 一定不是 $p$ 的原根。 -看上去复杂度好像很爆炸(可能确实是爆炸的,但一般情况下,最小的原根不会很大). +看上去复杂度好像很爆炸(可能确实是爆炸的,但一般情况下,最小的原根不会很大)。 -~~基于一个**假设**,原联系根是**均匀分布**的,我们**伪证明**一下总复杂度~~:原根数量定理:数 $p$ 要么没有原根,要么有 $\varphi(\varphi(p))$ 个原根. +~~基于一个**假设**,原联系根是**均匀分布**的,我们**伪证明**一下总复杂度~~:原根数量定理:数 $p$ 要么没有原根,要么有 $\varphi(\varphi(p))$ 个原根。 -由于 $p$ 是质数,所以 $p$ 有 $\varphi(p-1)$ 个原根,所以大概最小的原根为 $\frac{p}{\varphi(p-1)}=O(\log\log n)$,由于求每一个数时要枚举一遍 $p-1$ 所有的因数 $O(\sqrt p)$ 来判断其是否为原根,最后再算上 **BSGS** 的复杂度 $O(\sqrt{p})$,则复杂度约为 $O(\sqrt{p}\log \log n)$. +由于 $p$ 是质数,所以 $p$ 有 $\varphi(p-1)$ 个原根,所以大概最小的原根为 $\frac{p}{\varphi(p-1)}=O(\log\log n)$ ,由于求每一个数时要枚举一遍 $p-1$ 所有的因数 $O(\sqrt p)$ 来判断其是否为原根,最后再算上**BSGS**的复杂度 $O(\sqrt{p})$ ,则复杂度约为 $O(\sqrt{p}\log \log n)$ . -[BZOJ-1319](http://www.lydsy.com/JudgeOnline/problem.php?id=1319) 是一道模板题,代码可以在 [Steaunk 的博客](https://blog.csdn.net/Steaunk/article/details/78988376) 中看到. +[BZOJ-1319](http://www.lydsy.com/JudgeOnline/problem.php?id=1319)是一道模板题,代码可以在[Steaunk 的博客](https://blog.csdn.net/Steaunk/article/details/78988376)中看到。 ### 扩展篇 @@ -80,9 +80,8 @@ $\therefore r = 0$. 扩展 BSGS 用到了同余的一条性质: -令 $d=gcd(a,c) ,a=m \times d,b=n \times d,p=k \times d$; -则 $m \times d \equiv b \times d \pmod {c \times d}$ 等价于 $m \equiv n \pmod k$ -所以我们要先消除因子: +令 $d=gcd(a,c) ,a=m \times d,b=n \times d,p=k \times d$ ; +则 $m \times d \equiv b \times d \pmod {c \times d}$ 等价于 $m \equiv n \pmod k$ 所以我们要先消除因子: ```cpp d = 1, num = 0, t = 0; @@ -97,6 +96,6 @@ for (int t = gcd(a, c); t != 1; t = gcd(a, c)) { } ``` -消除完后,就变成了 $d \times m^{x-num} \equiv n \pmod k$,令 $x=i \times m+j+num$,后面的做法就和普通 BSGS 一样了。 +消除完后,就变成了 $d \times m^{x-num} \equiv n \pmod k$ ,令 $x=i \times m+j+num$ ,后面的做法就和普通 BSGS 一样了。 -注意,因为 $i,j \le 0$,所以 $x \le num$,但不排除解小于等于 $num$ 的情况,所以在消因子之前做一下 $\Theta(\log_2 p)$ 枚举,直接验证 $a^i \mod c = b$,这样就能避免这种情况。 +注意,因为 $i,j \le 0$ ,所以 $x \le num$ ,但不排除解小于等于 $num$ 的情况,所以在消因子之前做一下 $\Theta(\log_2 p)$ 枚举,直接验证 $a^i \mod c = b$ ,这样就能避免这种情况。 diff --git a/docs/math/cantor.md b/docs/math/cantor.md index ee34876a..deef18b8 100644 --- a/docs/math/cantor.md +++ b/docs/math/cantor.md @@ -12,13 +12,13 @@ 因为排列是按字典序排名的,因此越靠前的数字优先级越高。也就是说如果两个排列的某一位之前的数字都相同,那么如果这一位如果不相同,就按这一位排序。 -比如 $4$ 的排列,$[2,3,1,4]<[2,3,4,1]$,因为在第 $3$ 位出现不同,则 $[2,3,1,4]$ 的排名在 $[2,3,4,1]$ 前面。 +比如 $4$ 的排列, $[2,3,1,4]<[2,3,4,1]$ ,因为在第 $3$ 位出现不同,则 $[2,3,1,4]$ 的排名在 $[2,3,4,1]$ 前面。 ## 举个栗子 -我们知道长为 $5$ 的排列 $[2,5,3,4,1]$ 大于以 $1$ 为第一位的任何排列,以 $1$ 为第一位的 $5$ 的排列有 $4!$ 种。这是非常好理解的。但是我们对第二位的 $5$ 而言,它大于**第一位与这个排列相同的,而这一位比 $5$ 小的**所有排列。不过我们要注意的是,这一位不仅要比 $5$ 小,还要满足没有在当前排列的前面出现过,不然统计就重复了。因此这一位为 $1,3$ 或 $4$ ,第一位为 $2$ 的所有排列都比它要小,数量为 $3\times 3!$。 +我们知道长为 $5$ 的排列 $[2,5,3,4,1]$ 大于以 $1$ 为第一位的任何排列,以 $1$ 为第一位的 $5$ 的排列有 $4!$ 种。这是非常好理解的。但是我们对第二位的 $5$ 而言,它大于**第一位与这个排列相同的,而这一位比 $5$ 小的**所有排列。不过我们要注意的是,这一位不仅要比 $5$ 小,还要满足没有在当前排列的前面出现过,不然统计就重复了。因此这一位为 $1,3$ 或 $4$ ,第一位为 $2$ 的所有排列都比它要小,数量为 $3\times 3!$ 。 -按照这样统计下去,答案就是 $1+4!+3\times 3!+2!+1=46$。注意我们统计的是排名,因此最前面要 $+1$。 +按照这样统计下去,答案就是 $1+4!+3\times 3!+2!+1=46$ 。注意我们统计的是排名,因此最前面要 $+1$ 。 注意到我们每次要用到**当前有多少个小于它的数还没有出现**,这里用树状数组统计比它小的数出现过的次数就可以了。 @@ -30,12 +30,12 @@ ## 引用上面展开的例子 -首先让 $46-1=45$,$45$ 代表着有多少个排列比这个排列小。$\lfloor\frac {45}{4!}\rfloor=1$,有一个数小于它,所以第一位是 $2$。 +首先让 $46-1=45$ , $45$ 代表着有多少个排列比这个排列小。 $\lfloor\frac {45}{4!}\rfloor=1$ ,有一个数小于它,所以第一位是 $2$ 。 -此时让排名减去 $1\times 4!$得到$21$,$\lfloor\frac {21}{3!}\rfloor=3$,有 $3$ 个数小于它,去掉已经存在的 $2$,这一位是 $5$。 +此时让排名减去 $1\times 4!$ 得到 $21$ , $\lfloor\frac {21}{3!}\rfloor=3$ ,有 $3$ 个数小于它,去掉已经存在的 $2$ ,这一位是 $5$ 。 -$21-3\times 3!=3$,$\lfloor\frac {3}{2!}\rfloor=1$,有一个数小于它,那么这一位就是 $3$。 + $21-3\times 3!=3$ , $\lfloor\frac {3}{2!}\rfloor=1$ ,有一个数小于它,那么这一位就是 $3$ 。 -让 $3-1\times 2!=1$,有一个数小于它,这一位是剩下来的第二位,$4$,剩下一位就是 $1$。即 $[2,5,3,4,1]$。 +让 $3-1\times 2!=1$ ,有一个数小于它,这一位是剩下来的第二位, $4$ ,剩下一位就是 $1$ 。即 $[2,5,3,4,1]$ 。 -实际上我们得到了形如**有两个数小于它**这一结论,就知道它是当前第 $3$ 个没有被选上的数,这里也可以用线段树维护,时间复杂度为 $O(n\log n)$。 +实际上我们得到了形如**有两个数小于它**这一结论,就知道它是当前第 $3$ 个没有被选上的数,这里也可以用线段树维护,时间复杂度为 $O(n\log n)$ 。 diff --git a/docs/math/catalan.md b/docs/math/catalan.md index 72160cf9..80de29eb 100644 --- a/docs/math/catalan.md +++ b/docs/math/catalan.md @@ -1,25 +1,25 @@ ## Catalan 数列 -以下问题属于 Catalan 数列: +以下问题属于 Catalan 数列: -1. 有 $2n$ 个人排成一行进入剧场。入场费 5 元。其中只有$n$个人有一张 5 元钞票,另外 $n$ 人只有 10 元钞票,剧院无其它钞票,问有多少中方法使得只要有 10 元的人买票,售票处就有 5 元的钞票找零? +1. 有 $2n$ 个人排成一行进入剧场。入场费 5 元。其中只有 $n$ 个人有一张 5 元钞票,另外 $n$ 人只有 10 元钞票,剧院无其它钞票,问有多少中方法使得只要有 10 元的人买票,售票处就有 5 元的钞票找零? 2. 一位大城市的律师在她住所以北 $n$ 个街区和以东 $n$ 个街区处工作。每天她走 $2n$ 个街区去上班。如果他从不穿越(但可以碰到)从家到办公室的对角线,那么有多少条可能的道路? -3. 在圆上选择 $2n$ 个点, 将这些点成对连接起来使得所得到的 $n$ 条线段不相交的方法数? +3. 在圆上选择 $2n$ 个点,将这些点成对连接起来使得所得到的 $n$ 条线段不相交的方法数? 4. 对角线不相交的情况下,将一个凸多边形区域分成三角形区域的方法数? -5. 一个栈 (无穷大) 的进栈序列为 $1,2,3, \cdots ,n$ 有多少个不同的出栈序列? -6. $n$ 个结点可够造多少个不同的二叉树? -7. $n$ 个不同的数依次进栈,求不同的出栈结果的种数? -8. $n$ 个 $+1$ 和 $n$ 个 $-1$ 构成 $2n$ 项 $a_1,a_2, \cdots ,a_{2n}$,其部分和满足 $a_1+a_2+ \cdots +a_k>=0(k=1,2,3, \cdots ,2n)$ 对与 $n$ 该数列为? +5. 一个栈(无穷大)的进栈序列为 $1,2,3, \cdots ,n$ 有多少个不同的出栈序列? +6. $n$ 个结点可够造多少个不同的二叉树? +7. $n$ 个不同的数依次进栈,求不同的出栈结果的种数? +8. $n$ 个 $+1$ 和 $n$ 个 $-1$ 构成 $2n$ 项 $a_1,a_2, \cdots ,a_{2n}$ ,其部分和满足 $a_1+a_2+ \cdots +a_k>=0(k=1,2,3, \cdots ,2n)$ 对与 $n$ 该数列为? -其对应的序列为: +其对应的序列为: -| $H_0$ | $H_1$ | $H_2$ | $H_3$ | $H_4$ | $H_5$ | $H_6$ | ... | -| :---: | :---: | :---: | :---: | :---: | :---: | :---: | :-: | -| 1 | 1 | 2 | 5 | 14 | 42 | 132 | ... | +| $H_0$ | $H_1$ | $H_2$ | $H_3$ | $H_4$ | $H_5$ | $H_6$ | ... | +| :-----: | :-----: | :-----: | :-----: | :-----: | :-----: | :-----: | :-: | +| 1 | 1 | 2 | 5 | 14 | 42 | 132 | ... | (Catalan 数列) -该递推关系的解为: +该递推关系的解为: $$ H_n=\frac{C_{2n}^{n}}{n+1}(n=1,2,3,\cdots) diff --git a/docs/math/combination.md b/docs/math/combination.md index e24c20d1..6cf3db58 100644 --- a/docs/math/combination.md +++ b/docs/math/combination.md @@ -1,6 +1,6 @@ ## 排列组合简介 -排列组合是组合数学中的一种。排列就是指从给定个数的元素中取出指定个数的元素进行排序;组合则是指从给定个数的元素中仅仅取出指定个数的元素,不考虑排序。排列组合的中心问题是研究给定要求的排列和组合可能出现的情况总数。 排列组合与古典概率论关系密切。 +排列组合是组合数学中的一种。排列就是指从给定个数的元素中取出指定个数的元素进行排序;组合则是指从给定个数的元素中仅仅取出指定个数的元素,不考虑排序。排列组合的中心问题是研究给定要求的排列和组合可能出现的情况总数。排列组合与古典概率论关系密切。 在高中初等数学中,排列组合多是利用列表、枚举等方法解题。 @@ -10,7 +10,7 @@ ### 排列的定义 -从 $n$ 个不同元素中,任取 $m$($m≤n,m$ 与 $n$ 均为自然数, 下同)个元素按照一定的顺序排成一列,叫做从 $n$ 个不同元素中取出 $m$ 个元素的一个排列;从 $n$ 个不同元素中取出 $m$($m≤n$) 个元素的所有排列的个数,叫做从 $n$ 个不同元素中取出 $m$ 个元素的排列数,用符号 $A_n^m$ 表示。 +从 $n$ 个不同元素中,任取 $m$ ( $m≤n,m$ 与 $n$ 均为自然数,下同)个元素按照一定的顺序排成一列,叫做从 $n$ 个不同元素中取出 $m$ 个元素的一个排列;从 $n$ 个不同元素中取出 $m$ ( $m≤n$ ) 个元素的所有排列的个数,叫做从 $n$ 个不同元素中取出 $m$ 个元素的排列数,用符号 $A_n^m$ 表示。 ### 排列的计算公式 @@ -18,11 +18,11 @@ $$ A_n^m = n(n-1)(n-2) \cdots (n-m+1) = \frac{n!}{(n - m)!} $$ -**$n!$ 代表 $n$ 的阶乘,即 $6! = 1 \times 2 \times 3 \times 4 \times 5 \times 6$.** +** $n!$ 代表 $n$ 的阶乘,即 $6! = 1 \times 2 \times 3 \times 4 \times 5 \times 6$ .** ### 组合的定义 -从 $n$ 个不同元素中,任取 $m$($m≤n$) 个元素并成一组,叫做从 $n$ 个不同元素中取出 $m$ 个元素的一个组合;从 $n$ 个不同元素中取出 $m$($m≤n$) 个元素的所有组合的个数,叫做从 $n$ 个不同元素中取出 $m$ 个元素的组合数。用符号 $C_n^m$ 表示。 +从 $n$ 个不同元素中,任取 $m$ ( $m≤n$ ) 个元素并成一组,叫做从 $n$ 个不同元素中取出 $m$ 个元素的一个组合;从 $n$ 个不同元素中取出 $m$ ( $m≤n$ ) 个元素的所有组合的个数,叫做从 $n$ 个不同元素中取出 $m$ 个元素的组合数。用符号 $C_n^m$ 表示。 ### 组合的计算公式 @@ -34,15 +34,15 @@ $$ ### 排列 -** 全排列 **: -$n$ 个人全部来排队,队长为 $n$。第一个位置可以选 $n$ 个,第二位置可以选 $n-1$ 个,以此类推得: +**全排列**: + $n$ 个人全部来排队,队长为 $n$ 。第一个位置可以选 $n$ 个,第二位置可以选 $n-1$ 个,以此类推得: $$ A_n^n = n(n-1)(n-2) \cdots 3 × 2 × 1 = n! $$ -** 部分排列 **: -$n$ 个人选 $m$ 个来排队 ($m \le n$)。第一个位置可以选 $n$ 个,第二位置可以选 $n-1$ 个,以此类推,第 $m$ 个(最后一个)可以选 $n-m+1$ 个,得: +**部分排列**: + $n$ 个人选 $m$ 个来排队 ( $m \le n$ )。第一个位置可以选 $n$ 个,第二位置可以选 $n-1$ 个,以此类推,第 $m$ 个(最后一个)可以选 $n-m+1$ 个,得: $$ A_n^m = n(n-1)(n-2) \cdots (n-m+1) = \frac{n!}{(n - m)!} @@ -50,7 +50,7 @@ $$ ### 组合 -$n$ 个人 $m$($m \le n$) 个出来,不排队,不在乎顺序 $C_n^m$。如果在乎排列那么就是 $A_n^m$,如果不在乎那么就要除掉重复,那么重复了多少?同样选出的来的 $m$ 个人,他们还要 “全排” 得 $A_n^m$,所以得: + $n$ 个人 $m$ ( $m \le n$ ) 个出来,不排队,不在乎顺序 $C_n^m$ 。如果在乎排列那么就是 $A_n^m$ ,如果不在乎那么就要除掉重复,那么重复了多少?同样选出的来的 $m$ 个人,他们还要“全排”得 $A_n^m$ ,所以得: $$ C_n^m \times m! = A_n^m @@ -74,14 +74,14 @@ $$ ### 圆排列 -$n$ 个人全部来围成一圈为 $Q_n^n$,其中已经排好的一圈,从不同位置断开,又变成不同的队列。 + $n$ 个人全部来围成一圈为 $Q_n^n$ ,其中已经排好的一圈,从不同位置断开,又变成不同的队列。 所以: $$ Q_n^n \times n = A_n^n → Q_n = \frac{A_n^n}{n} = (n-1)! $$ -** 由此可知:部分圆排 ** +**由此可知:部分圆排** $$ Q_n^r = \frac{A_n^r}{r} = \frac{n!}{r \times (n-r)!} @@ -89,7 +89,7 @@ $$ ### 重复排列(有限) -$k$ 种不一样的球,每种球的个数分别是 $a_1,a_2,\cdots,a_k$,设 $n=a_1+a_2+…+a_k$,这 $n$ 个球的全排列数,为 + $k$ 种不一样的球,每种球的个数分别是 $a_1,a_2,\cdots,a_k$ ,设 $n=a_1+a_2+…+a_k$ ,这 $n$ 个球的全排列数,为 $$ \frac{n!}{a_1! \times a_2! \times \cdots \times a_k!} @@ -97,7 +97,7 @@ $$ ### 重复组合(无限) -$n$ 种不一样的球,每种球的个数是无限的, 从中选 $k$ 个出来,不用排列,是组合,为 $C_{n+k-1}^{k}$. + $n$ 种不一样的球,每种球的个数是无限的,从中选 $k$ 个出来,不用排列,是组合,为 $C_{n+k-1}^{k}$ . 证明: @@ -113,41 +113,40 @@ $$ 1 \le b_1 < b_2+1 < b_3+2 < b_4+3 < \cdots < b_k+k-1 \le n+k-1 $$ -中间还是 $k$ 个数!不过已经不是 $b$ 系列,而是 $c$ 系列,** 假设 $c[i]=b[i]+i-1$,所以 ** +中间还是 $k$ 个数!不过已经不是 $b$ 系列,而是 $c$ 系列,**假设 $c[i]=b[i]+i-1$ ,所以** $$ 1 \le c_1 < c_2 < c_3 < c_4 < \cdots < c_k \le n+k-1 $$ -所以问题就开始转换为无重复组合问题,即在 $n+k-1$ 个元素中选中 $k$ 个的组合数 $C_{n+k-1}^{k}$。 +所以问题就开始转换为无重复组合问题,即在 $n+k-1$ 个元素中选中 $k$ 个的组合数 $C_{n+k-1}^{k}$ 。 ### 不相邻的排列 -$1 \sim n$ 这 $n$ 个自然数中选 $k$ 个,这 $k$ 个数中任何两个数不相邻数的组合有 $C_{n-k+1}^{k}$ 种。 + $1 \sim n$ 这 $n$ 个自然数中选 $k$ 个,这 $k$ 个数中任何两个数不相邻数的组合有 $C_{n-k+1}^{k}$ 种。 证明和上面的相同(其实就是懒得写),请自行证明 XD ### 错位排列(错排) 先看一个小问题: -$5$ 本书,编号分别是 $1,2,3,4,5$,现在要把这 5 本书是放在编号 $1,2,3,4,5$ 的书架上,要求书的编号和书架的编号不一样,请问有多少种不一样的放置方法? + $5$ 本书,编号分别是 $1,2,3,4,5$ ,现在要把这 5 本书是放在编号 $1,2,3,4,5$ 的书架上,要求书的编号和书架的编号不一样,请问有多少种不一样的放置方法? 再看一个小问题: 胸口贴着编号为 $1,2,\cdots,n$ 的 $n$ 个球员分别住在编号为 $1,2,\cdots,n$ 的 $n$ 个房间里面。现规定每个人住一个房间,自己的编号不能和房间的编号一样。 这就是错排问题。当 $n=3$ 时,只能为 312 或 231 这两种。 -那么错排问题的解题思路是什么呢?我们以第二个问题为例: -** 递推还是王道!!!** +那么错排问题的解题思路是什么呢?我们以第二个问题为例:**递推还是王道!** 刚开始所有球员都住在和自己编号一样的房间里面。然后错排开始了,第 $n$ 个球员从第 $n$ 个房间出来。 第一种情况: -$n$ 想和 $i(1 \le i \le n-1)$ 其中任何一个球员换房间,其他 $n-2$ 个人换房间的事情,他们就不管了。其他 $n-2$ 个球员的的错排数为 $d[n-2]$,$n$ 可以和前面 $1 \sim n-1$ 对换,所以有 $n-1$ 个 $d[n-2]$。 + $n$ 想和 $i(1 \le i \le n-1)$ 其中任何一个球员换房间,其他 $n-2$ 个人换房间的事情,他们就不管了。其他 $n-2$ 个球员的的错排数为 $d[n-2]$ , $n$ 可以和前面 $1 \sim n-1$ 对换,所以有 $n-1$ 个 $d[n-2]$ 。 第二种情况: -$n$ 想和 $i(1 \le i \le n-1)$ 其中任何一个球员换房间,但是 $n$ 只想 $i$ 住在第 $N$ 个房间,而 $n$ 不想住第 $I$ 个房间。 + $n$ 想和 $i(1 \le i \le n-1)$ 其中任何一个球员换房间,但是 $n$ 只想 $i$ 住在第 $N$ 个房间,而 $n$ 不想住第 $I$ 个房间。 -可能你会这样想:那么 $n$ 可以让 $j$ 住在第 $I$ 号房间里面,然后 $n$ 住在房间 $J$。抱歉,$j(1 \le j \le n-1,j\neq i)$ 生气 $n$ 为什么一开始就去找 $i$ 不直接来找 $j$。没办法,$n$ 把自己胸口的编码 $N$ 换成了 $I$,他假装自己是 $i$,然后错排 $1 \sim n-1$(也就是 $d[n-2]$)的时候参与进去,这样自己就不会呆在第 $I$ 号房间了。所以有 $n-1$ 个 $d[n-1]$。 +可能你会这样想:那么 $n$ 可以让 $j$ 住在第 $I$ 号房间里面,然后 $n$ 住在房间 $J$ 。抱歉, $j(1 \le j \le n-1,j\neq i)$ 生气 $n$ 为什么一开始就去找 $i$ 不直接来找 $j$ 。没办法, $n$ 把自己胸口的编码 $N$ 换成了 $I$ ,他假装自己是 $i$ ,然后错排 $1 \sim n-1$ (也就是 $d[n-2]$ )的时候参与进去,这样自己就不会呆在第 $I$ 号房间了。所以有 $n-1$ 个 $d[n-1]$ 。 如果理解了以上内容,那么错排的公式就出来了: @@ -161,23 +160,23 @@ $$ d_n = n \times d_{n-1} + (-1)^n $$ -错位排列数列为 $0,1,2,9,44,265,\cdots$ +错位排列数列为 $0,1,2,9,44,265,\cdots$ ## 加法 & 乘法原理 ### 加法原理 -完成一个工程可以有 $n$ 类办法,$a[i](1 \le i \le n)$ 代表第 $i$ 类方法的数目。 +完成一个工程可以有 $n$ 类办法, $a[i](1 \le i \le n)$ 代表第 $i$ 类方法的数目。 那么完成这件事共有 $S=a[1]+a[2]+\cdots +a[n]$ 种不同的方法。 ### 乘法原理 -完成一个工程需要分 $n$ 个步骤,$a[i](1 \le i \le n)$ 代表第 $i$ 个步骤的不同方法数目。 +完成一个工程需要分 $n$ 个步骤, $a[i](1 \le i \le n)$ 代表第 $i$ 个步骤的不同方法数目。 那么完成这件事共有 $S = a[1] \times a[2] \times \cdots \times a[n]$ 种不同的方法。 ### 两原理的区别 -** 一个与分类有关,一个与分步有关;加法原理是 “分类完成”,乘法原理是 “分步完成”。** +**一个与分类有关,一个与分步有关;加法原理是“分类完成”,乘法原理是“分步完成”。** ## 几个关于组合的公式 diff --git a/docs/math/complex.md b/docs/math/complex.md index eecf0d63..5956659a 100644 --- a/docs/math/complex.md +++ b/docs/math/complex.md @@ -10,13 +10,13 @@ 我们在实数域中,说 $x^2+1=0$ 这个二次方程无解。这个方程无解,那么我们能不能强行让它有解?如果让它有解的话,确实我们解决了问题,但是这个解的意义是什么? -我们尝试一下,定义一个新数 $\text{i}$,$\text{i}^2+1=0$,那么 $x^2+1=0$ 就有一个解 $x=\text{i}$ 了。 +我们尝试一下,定义一个新数 $\text{i}$ , $\text{i}^2+1=0$ ,那么 $x^2+1=0$ 就有一个解 $x=\text{i}$ 了。 我们希望引入的这个新数与实数域中的数一样,能与实数进行加法和乘法运算,还保留各种运算律。 那么我们很容易想到 $a+b\text{i}$ 这种形式,当然其中 $a,b$ 都是实数。把 $\text{i}$ 看做类似变量的东西,验证其运算性质。我们可以发现,得到的结果全部有着 $a+b\text{i}$ 的类似形式。 -那么这样的性质就与实数域类似了,我们把所有有着 $a+b\text{i}$ 形式的数放入一个集合中,就出现了复数集 $\mathbb{C}=\{a+b\text{i} \mid a,b\in \mathbb{R}\}$。 +那么这样的性质就与实数域类似了,我们把所有有着 $a+b\text{i}$ 形式的数放入一个集合中,就出现了复数集 $\mathbb{C}=\{a+b\text{i} \mid a,b\in \mathbb{R}\}$ 。 我们可以发现,这个集合中的数和实数集中的数类似,都有在集合中任选两个数进行四则运算,得到的数都是原集合中的数的性质。我们说复数集对于四则运算是**封闭的**。 @@ -24,11 +24,11 @@ > 哇哦我们定义的数的性质这么好! -我们定义形如 $a+b\text{i}$,其中 $a,b\in \mathbb{R}$ 的数叫做**复数**,其中 $\text{i}$ 被称为**虚数单位**,全体复数的集合叫做**复数集**。 +我们定义形如 $a+b\text{i}$ ,其中 $a,b\in \mathbb{R}$ 的数叫做**复数**,其中 $\text{i}$ 被称为**虚数单位**,全体复数的集合叫做**复数集**。 -复数通常用 $z$ 表示,即 $z=a+b\text{i}$。这种形式被称为**复数的代数形式**。其中 $a$ 称为复数 $z$ 的**实部**,$b$ 称为复数 $z$ 的**虚部**。如无特殊说明,都有 $a,b\in \mathbb{R}$。 +复数通常用 $z$ 表示,即 $z=a+b\text{i}$ 。这种形式被称为**复数的代数形式**。其中 $a$ 称为复数 $z$ 的**实部**, $b$ 称为复数 $z$ 的**虚部**。如无特殊说明,都有 $a,b\in \mathbb{R}$ 。 -对于一个复数 $z$,当且仅当 $b=0$ 时,它是实数,当 $b\not = 0$ 时,它是虚数,当 $a=0$ 且 $b\not = 0$ 时,它是纯虚数。 +对于一个复数 $z$ ,当且仅当 $b=0$ 时,它是实数,当 $b\not = 0$ 时,它是虚数,当 $a=0$ 且 $b\not = 0$ 时,它是纯虚数。 纯虚数,虚数,实数,复数的关系如下图所示。 @@ -45,19 +45,19 @@ 我们把所有实数都放在了数轴上,并且发现数轴上的点与实数一一对应。我们考虑对复数也这样处理。 -首先我们定义**复数相等**:两个复数 $z_1=a+b\text{i},z_2=c+d\text{i}$ 是相等的,当且仅当 $a=c$ 且 $b=d$。 +首先我们定义**复数相等**:两个复数 $z_1=a+b\text{i},z_2=c+d\text{i}$ 是相等的,当且仅当 $a=c$ 且 $b=d$ 。 这么定义是十分自然的,在此不做过多解释。 -也就是说,我们可以用唯一的有序实数对 $(a,b)$ 表示一个复数 $z=a+b\text{i}$。这样,联想到平面直角坐标系,我们可以发现**复数集与平面直角坐标系中的点集一一对应**。好了,我们找到了复数的一种几何意义。 +也就是说,我们可以用唯一的有序实数对 $(a,b)$ 表示一个复数 $z=a+b\text{i}$ 。这样,联想到平面直角坐标系,我们可以发现**复数集与平面直角坐标系中的点集一一对应**。好了,我们找到了复数的一种几何意义。 -那么这个平面直角坐标系就不再一般,因为平面直角坐标系中的点具有了特殊意义——表示一个复数,所以我们把这样的平面直角坐标系称为**复平面**,$x$ 轴称为**实轴**,$y$ 轴称为**虚轴**。我们进一步地说:**复数集与复平面内所有的点所构成的集合是一一对应的**。 +那么这个平面直角坐标系就不再一般,因为平面直角坐标系中的点具有了特殊意义——表示一个复数,所以我们把这样的平面直角坐标系称为**复平面**, $x$ 轴称为**实轴**, $y$ 轴称为**虚轴**。我们进一步地说:**复数集与复平面内所有的点所构成的集合是一一对应的**。 -我们考虑到学过的平面向量的知识,发现向量的坐标表示也是一个有序实数对 $(a,b)$,显然,复数 $z=a+b\text{i}$ 对应复平面内的点 $Z(a,b)$,那么它还对应平面向量 $\overrightarrow{OZ}=(a,b)$,于是我们又找到了复数的另一种几何意义:**复数集与复平面内的向量所构成的集合是一一对应的(实数 $0$ 与零向量对应)**。 +我们考虑到学过的平面向量的知识,发现向量的坐标表示也是一个有序实数对 $(a,b)$ ,显然,复数 $z=a+b\text{i}$ 对应复平面内的点 $Z(a,b)$ ,那么它还对应平面向量 $\overrightarrow{OZ}=(a,b)$ ,于是我们又找到了复数的另一种几何意义:**复数集与复平面内的向量所构成的集合是一一对应的(实数 $0$ 与零向量对应)**。 -于是,我们由向量的知识迁移到复数上来,定义**复数的模**就是复数所对应的向量的模。复数 $z=a+b\text{i}$ 的模 $|z|=\sqrt{a^2+b^2}$。 +于是,我们由向量的知识迁移到复数上来,定义**复数的模**就是复数所对应的向量的模。复数 $z=a+b\text{i}$ 的模 $|z|=\sqrt{a^2+b^2}$ 。 -于是为了方便,我们常把复数 $z=a+b\text{i}$ 称为点 $Z$ 或向量 $\overrightarrow {OZ}$,并规定相等的向量表示同一个复数。 +于是为了方便,我们常把复数 $z=a+b\text{i}$ 称为点 $Z$ 或向量 $\overrightarrow {OZ}$ ,并规定相等的向量表示同一个复数。 并且由向量的知识我们发现,虚数不可以比较大小(但是实数是可以的)。 @@ -67,7 +67,7 @@ 我们规定,复数的加法规则如下: -设 $z_1=a+b\text{i},z_2=c+d\text{i}$,那么 +设 $z_1=a+b\text{i},z_2=c+d\text{i}$ ,那么 $$ z_1+z_2=(a+c)+(b+d)\text{i} @@ -96,7 +96,7 @@ $$ 我们规定,复数的加法规则如下: -设 $z_1=a+b\text{i},z_2=c+d\text{i}$,那么 +设 $z_1=a+b\text{i},z_2=c+d\text{i}$ ,那么 $$ \begin{aligned} @@ -106,7 +106,7 @@ z_1z_2&=(a+b\text{i})(c+d\text{i})\\ \end{aligned} $$ -可以看出,两个复数相乘类似于两个多项式相乘,只需要把 $\text{i}^2$ 换成 $-1$,并将实部与虚部分别合并即可。 +可以看出,两个复数相乘类似于两个多项式相乘,只需要把 $\text{i}^2$ 换成 $-1$ ,并将实部与虚部分别合并即可。 复数确实与多项式有关,因为复数域是实系数多项式环模掉 $x^2+1$ 生成的理想。(这句话不明白其实也没有关系) @@ -131,8 +131,8 @@ $$ \end{aligned} $$ -为了分母实数化,我们乘了一个 $c-d\text{i}$,这个式子很有意义。 +为了分母实数化,我们乘了一个 $c-d\text{i}$ ,这个式子很有意义。 -我们定义,当两个虚数实部相等,虚部互为相反数时,这两个复数互为**共轭复数**。通常记 $z=a+b\text{i}$ 的共轭复数为 $\bar z=a-b\text{i}$。我们可以发现,两个复数互为共轭复数,那么它们**关于实轴对称**。 +我们定义,当两个虚数实部相等,虚部互为相反数时,这两个复数互为**共轭复数**。通常记 $z=a+b\text{i}$ 的共轭复数为 $\bar z=a-b\text{i}$ 。我们可以发现,两个复数互为共轭复数,那么它们**关于实轴对称**。 由于向量没有除法,这里不讨论与向量的关系。 diff --git a/docs/math/crt.md b/docs/math/crt.md index b56aecd4..77f44d03 100644 --- a/docs/math/crt.md +++ b/docs/math/crt.md @@ -2,13 +2,13 @@ > 有物不知其数,三三数之剩二,五五数之剩三,七七数之剩二。问物几何? -即求满足以下条件的整数:除以 $3$ 余 $2$,除以 $5$ 余 $3$,除以 $7$ 余 $2$。 +即求满足以下条件的整数:除以 $3$ 余 $2$ ,除以 $5$ 余 $3$ ,除以 $7$ 余 $2$ 。 该问题最早见于《孙子算经》中,并有该问题的具体解法。宋朝数学家秦九韶于 1247 年《数书九章》卷一、二《大衍类》对「物不知数」问题做出了完整系统的解答。上面具体问题的解答口诀由明朝数学家程大位在《算法统宗》中给出: > 三人同行七十希,五树梅花廿一支,七子团圆正半月,除百零五便得知。 -$2\times 70+3\times 21+2\times 15=233=2\times 105+23$,故答案为 $23$。 + $2\times 70+3\times 21+2\times 15=233=2\times 105+23$ ,故答案为 $23$ 。 ## 算法简介及过程 @@ -29,12 +29,12 @@ $$ ### 算法流程 -1. 计算所有模数的积 $n$; +1. 计算所有模数的积 $n$ ; 2. 对于第 $i$ 个方程: - 1. 计算 $m_i=\frac{n}{n_i}$; - 2. 计算 $m_i$ 在模 $n_i$ 意义下的[逆元](/math/inverse/) $m_i^{-1}$; - 3. 计算 $c_i=m_im_i^{-1}$(**不要对 $n_i$ 取模**)。 -3. 方程组的唯一解为:$a=\sum_{i=1}^k a_ic_i \pmod n$。 + 1. 计算 $m_i=\frac{n}{n_i}$ ; + 2. 计算 $m_i$ 在模 $n_i$ 意义下的[逆元](/math/inverse/) $m_i^{-1}$ ; + 3. 计算 $c_i=m_im_i^{-1}$ (**不要对 $n_i$ 取模**)。 +3. 方程组的唯一解为: $a=\sum_{i=1}^k a_ic_i \pmod n$ 。 ### 伪代码 @@ -52,9 +52,9 @@ return ans ## 算法的证明 -我们需要证明上面算法计算所得的 $a$ 对于任意 $i=1,2,\cdots,k$ 满足 $a\equiv a_i \pmod {n_i}$。 +我们需要证明上面算法计算所得的 $a$ 对于任意 $i=1,2,\cdots,k$ 满足 $a\equiv a_i \pmod {n_i}$ 。 -当 $i\neq j$ 时,有 $m_j\equiv 0 \pmod {n_i}$,故 $c_j\equiv m_j\equiv 0 \pmod {n_i}$。又有 $c_i\equiv m_i(m_i^{-1}\bmod {n_i})\equiv 1 \pmod {n_i}$,所以我们有: +当 $i\neq j$ 时,有 $m_j\equiv 0 \pmod {n_i}$ ,故 $c_j\equiv m_j\equiv 0 \pmod {n_i}$ 。又有 $c_i\equiv m_i(m_i^{-1}\bmod {n_i})\equiv 1 \pmod {n_i}$ ,所以我们有: $$ \begin{aligned} @@ -65,11 +65,11 @@ a&\equiv \sum_{j=1}^k a_jc_j &\pmod {n_i} \\ \end{aligned} $$ -**即对于任意 $i=1,2,\cdots,k$,上面算法得到的 $a$ 总是满足 $a\equiv a_i \pmod{n_i}$,即证明了解同余方程组的算法的正确性。** +**即对于任意 $i=1,2,\cdots,k$ ,上面算法得到的 $a$ 总是满足 $a\equiv a_i \pmod{n_i}$ ,即证明了解同余方程组的算法的正确性。** -因为我们没有对输入的 $a_i$ 作特殊限制,所以任何一组输入 $\{a_i\}$ 都对应一个解 $a$。 +因为我们没有对输入的 $a_i$ 作特殊限制,所以任何一组输入 $\{a_i\}$ 都对应一个解 $a$ 。 -另外,若 $x\neq y$,则总存在 $i$ 使得 $x$ 和 $y$ 在模 $n_i$ 下不同余。 +另外,若 $x\neq y$ ,则总存在 $i$ 使得 $x$ 和 $y$ 在模 $n_i$ 下不同余。 **故系数列表 $\{a_i\}$ 与解 $a$ 之间是一一映射关系,方程组总是有唯一解。** @@ -77,11 +77,11 @@ $$ 下面演示 CRT 如何解「物不知数」问题。 -1. $n=3\times 5\times 7=105$; -2. 三人同行**七十**希:$n_1=3, m_1=n/n_1=35, m_1^{-1}\equiv 2\pmod 3$,故 $c_1=35\times 2=70$; -3. 五树梅花**廿一**支:$n_2=5, m_2=n/n_2=21, m_2^{-1}\equiv 1\pmod 5$,故 $c_2=21\times 1=21$; -4. 七子团圆正**半月**:$n_3=7, m_3=n/n_3=15, m_3^{-1}\equiv 1\pmod 7$,故 $c_3=15\times 1=15$; -5. 所以方程组的唯一解为 $a\equiv 2\times 70+3\times 21+2\times 15\equiv 233\equiv 23 \pmod {105}$。(除**百零五**便得知) +1. $n=3\times 5\times 7=105$ ; +2. 三人同行**七十**希: $n_1=3, m_1=n/n_1=35, m_1^{-1}\equiv 2\pmod 3$ ,故 $c_1=35\times 2=70$ ; +3. 五树梅花**廿一**支: $n_2=5, m_2=n/n_2=21, m_2^{-1}\equiv 1\pmod 5$ ,故 $c_2=21\times 1=21$ ; +4. 七子团圆正**半月**: $n_3=7, m_3=n/n_3=15, m_3^{-1}\equiv 1\pmod 7$ ,故 $c_3=15\times 1=15$ ; +5. 所以方程组的唯一解为 $a\equiv 2\times 70+3\times 21+2\times 15\equiv 233\equiv 23 \pmod {105}$ 。(除**百零五**便得知) ## 应用 @@ -95,7 +95,7 @@ $$ ## 比较两 CRT 下整数 -考虑 CRT, 不妨假设$n_1\leq n_2 \leq ... \leq n_k$ +考虑 CRT, 不妨假设 $n_1\leq n_2 \leq ... \leq n_k$ $$ \left\{ \begin{array} { r l } { x } & { \equiv a _ { 1 } \left( \bmod n _ { 1 } \right) } \\ { x } & { \equiv a _ { 2 } \left( \bmod n _ { 2 } \right) } \\ { } & { \vdots } \\ { x } & { \equiv a _ { n } \left( \bmod n _ { k } \right) } \end{array} \right. @@ -103,9 +103,9 @@ $$ 与 PMR(Primorial Mixed Radix) 表示 -$x=b_1+b_2n_1+b_3n_1n_2...+b_kn_1n_2...n_{k-1} ,b_i\in [0,n_i)$ + $x=b_1+b_2n_1+b_3n_1n_2...+b_kn_1n_2...n_{k-1} ,b_i\in [0,n_i)$ -将数字转化到 PMR 下, 逐位比较即可 +将数字转化到 PMR 下,逐位比较即可 转化方法考虑依次对 PMR 取模 @@ -119,21 +119,21 @@ b_k&=(...((a_k-b_1)c_{1,k}-b_2)c_{2,k})-...)c_{k-1,k} \mod n_k \end{aligned} $$ -其中$c_{i,j}$表示$n_i$对$n_j$的逆元,$c_{i,j}n_i=1 \mod n_j$ +其中 $c_{i,j}$ 表示 $n_i$ 对 $n_j$ 的逆元, $c_{i,j}n_i=1 \mod n_j$ ## 扩展:模数不互质的情况 ### 两个方程 -设两个方程分别是 $x\equiv a_1 \pmod {m_1}$、$x\equiv a_2 \pmod {m_2}$; +设两个方程分别是 $x\equiv a_1 \pmod {m_1}$ 、 $x\equiv a_2 \pmod {m_2}$ ; -将它们转化为不定方程:$x=m_1p+a_1=m_2q+a_2$,其中 $p, q$ 是整数,则有 $m_1p-m_2q=a_2-a_1$。 +将它们转化为不定方程: $x=m_1p+a_1=m_2q+a_2$ ,其中 $p, q$ 是整数,则有 $m_1p-m_2q=a_2-a_1$ 。 由裴蜀定理,当 $a_2-a_1$ 不能被 $\gcd(m_1,m_2)$ 整除时,无解; -其他情况下,可以通过扩展欧几里得算法解出来一组可行解 $(p, q)$; +其他情况下,可以通过扩展欧几里得算法解出来一组可行解 $(p, q)$ ; -则原来的两方程组成的模方程组的解为 $x\equiv b\pmod M$,其中 $b=m_1p+a_1$,$M=\text{lcm}(m_1, m_2)$。 +则原来的两方程组成的模方程组的解为 $x\equiv b\pmod M$ ,其中 $b=m_1p+a_1$ , $M=\text{lcm}(m_1, m_2)$ 。 ### 多个方程 @@ -143,8 +143,8 @@ $$ [【模板】扩展中国剩余定理](https://www.luogu.org/problemnew/show/P4777) -[\[NOI2018\] 屠龙勇士](https://www.luogu.org/problemnew/show/P4774) +[\[NOI2018\]屠龙勇士](https://www.luogu.org/problemnew/show/P4774) -[\[TJOI2009\] 猜数字](https://www.luogu.org/problemnew/show/P3868) +[\[TJOI2009\]猜数字](https://www.luogu.org/problemnew/show/P3868) -[\[SDOI2010\] 古代猪文](https://www.luogu.org/problemnew/show/P2480) +[\[SDOI2010\]古代猪文](https://www.luogu.org/problemnew/show/P2480) diff --git a/docs/math/dictionary.md b/docs/math/dictionary.md index 760c9b28..8ac29600 100644 --- a/docs/math/dictionary.md +++ b/docs/math/dictionary.md @@ -1,14 +1,14 @@ -在学习之前请先学习 [分块](/ds/square-root-decomposition/)。 +在学习之前请先学习[分块](/ds/square-root-decomposition/)。 打表大家都知道,就是在比赛时把答案都输出出来,然后开个数组,把答案直接存入数组里。于是你的代码时间复杂度就是 $O(1)$ 的了。 -但是需要注意这个技巧只适用于类似输出某函数值类的问题。比如规定 $f(x)$ 为整数 $x$ 的二进制表示中 $1$ 的个数。输入一个正整数 $n$,输出 $\sum_{i=1}^nf^2(i)$。这样的话 $n$ 不大时,采用打表的方法可以做到 $O(1)$ 的复杂度。 +但是需要注意这个技巧只适用于类似输出某函数值类的问题。比如规定 $f(x)$ 为整数 $x$ 的二进制表示中 $1$ 的个数。输入一个正整数 $n$ ,输出 $\sum_{i=1}^nf^2(i)$ 。这样的话 $n$ 不大时,采用打表的方法可以做到 $O(1)$ 的复杂度。 -注意到这个问题其实十分的简单,采用一般做法也可以做到 $O(n\log n)$ 的复杂度,但是 $n=10^9$? +注意到这个问题其实十分的简单,采用一般做法也可以做到 $O(n\log n)$ 的复杂度,但是 $n=10^9$ ? -还有一些时候,打出来的表十分大,如果对于每一个 $n$,都输出 $f(n)$ 的话,那么 MLE 之外,还有可能代码超过最大代码长度限制,导致编译前不通过(代码可能直接被 pass)。 +还有一些时候,打出来的表十分大,如果对于每一个 $n$ ,都输出 $f(n)$ 的话,那么 MLE 之外,还有可能代码超过最大代码长度限制,导致编译前不通过(代码可能直接被 pass)。 -我们考虑优化这个答案表,借用分块思想,我们设置一个合理的步长 $m$(这个步长一般视代码长度而定),对于第 $i$ 块,输出: +我们考虑优化这个答案表,借用分块思想,我们设置一个合理的步长 $m$ (这个步长一般视代码长度而定),对于第 $i$ 块,输出: $$ \Large \sum_{k=\frac{n}{m}(i-1)+1}^{\frac{ni}{m}} f^2(k) @@ -29,7 +29,7 @@ $$ [「BZOJ 3798」特殊的质数](https://www.lydsy.com/JudgeOnline/problem.php?id=3798):权限题……~~不过可以在各大 BZ 离线题库中看到。~~ -[题意简述](https://www.zhihu.com/question/60674478/answer/180805562):求 $[l,r]$ 区间内有多少个质数可以分解为两个正整数的平方和。—— via PoPoQQQ +[题意简述](https://www.zhihu.com/question/60674478/answer/180805562):求 $[l,r]$ 区间内有多少个质数可以分解为两个正整数的平方和。——via PoPoQQQ [「Luogu P1822」魔法指纹](https://www.luogu.org/problem/show?pid=P1822):其实是一道暴搜,不过可以练练分段打表。 diff --git a/docs/math/du-sieves.md b/docs/math/du-sieves.md index 23ea313b..f817ceef 100644 --- a/docs/math/du-sieves.md +++ b/docs/math/du-sieves.md @@ -1,64 +1,64 @@ -在数论题目中,常常需要根据一些 ** 积性函数 ** 的性质,求出一些式子的值。 +在数论题目中,常常需要根据一些**积性函数**的性质,求出一些式子的值。 -** 积性函数 ** : 对于所有互质的 $a$ 和 $b$ ,总有 $f(ab)=f(a)f(b)$ ,则称 $f(x)$ 为积性函数。 +**积性函数**:对于所有互质的 $a$ 和 $b$ ,总有 $f(ab)=f(a)f(b)$ ,则称 $f(x)$ 为积性函数。 常见的积性函数有: -$d(x)=\sum_{i|n} 1$ + $d(x)=\sum_{i|n} 1$ -$\sigma(x)=\sum_{i|n} i$ + $\sigma(x)=\sum_{i|n} i$ -$\varphi(x)=\sum_{i=1}^x 1[gcd(x,i)=1]$ + $\varphi(x)=\sum_{i=1}^x 1[gcd(x,i)=1]$ -$\mu(x)=\begin{cases}1&\text{n=1}\\(-1)^k& \ \prod_{i=1}^k q_i=1\\0 &\ \max\{q_i\}>1\end{cases}$ + $\mu(x)=\begin{cases}1&\text{n=1}\\(-1)^k& \ \prod_{i=1}^k q_i=1\\0 &\ \max\{q_i\}>1\end{cases}$ 积性函数有如下性质: 若 $f(x)$ , $g(x)$ 为积性函数,则 -$h(x)=f(x^p)$ + $h(x)=f(x^p)$ -$h(x)=f^p(x)$ + $h(x)=f^p(x)$ -$h(x)=f(x)g(x)$ + $h(x)=f(x)g(x)$ -$h(x)=\sum_{d|x} f(d)g(\frac x d)$ + $h(x)=\sum_{d|x} f(d)g(\frac x d)$ 中的 $h(x)$ 也为积性函数。 -在莫比乌斯反演的题目中,往往要求出一些数论函数的前缀和,利用 ** 杜教筛 ** 可以快速求出这些前缀和。 +在莫比乌斯反演的题目中,往往要求出一些数论函数的前缀和,利用**杜教筛**可以快速求出这些前缀和。 -??? note " 例题 [P4213 【模板】杜教筛( Sum ) ](https://www.luogu.org/problemnew/show/P4213)" +??? note " 例题[P4213【模板】杜教筛(Sum)](https://www.luogu.org/problemnew/show/P4213)" -题目大意: 求 $S_1(n)= \sum_{i=1}^n \mu(i)$ 和 $S_2(n)= \sum_{i=1}^n \varphi(i)$ 的值, $n\le 2^{31} -1$ 。 +题目大意:求 $S_1(n)= \sum_{i=1}^n \mu(i)$ 和 $S_2(n)= \sum_{i=1}^n \varphi(i)$ 的值, $n\le 2^{31} -1$ 。 -由 ** 狄利克雷卷积 ** ,我们知道: +由**狄利克雷卷积**,我们知道: -$\because \epsilon =\mu * 1$ ( $\epsilon(n)=~[n=1]$ ) + $\because \epsilon =\mu * 1$ ( $\epsilon(n)=~[n=1]$ ) -$\therefore \epsilon (n)=\sum_{d|n} \mu(d)$ + $\therefore \epsilon (n)=\sum_{d|n} \mu(d)$ -$S_1(n)=\sum_{i=1}^n \epsilon (i)-\sum_{i=2}^n S_1(\lfloor \frac n i \rfloor)$ + $S_1(n)=\sum_{i=1}^n \epsilon (i)-\sum_{i=2}^n S_1(\lfloor \frac n i \rfloor)$ -$= 1-\sum_{i=2}^n S_1(\lfloor \frac n i \rfloor)$ + $= 1-\sum_{i=2}^n S_1(\lfloor \frac n i \rfloor)$ -观察到 $\lfloor \frac n i \rfloor$ 最多只有 $O(\sqrt n)$ 种取值,我们就可以应用 ** 整除分块 ** (或称数论分块)来计算每一项的值了。 +观察到 $\lfloor \frac n i \rfloor$ 最多只有 $O(\sqrt n)$ 种取值,我们就可以应用**整除分块**(或称数论分块)来计算每一项的值了。 直接计算的时间复杂度为 $O(n^{\frac 3 4})$ 。考虑先线性筛预处理出前 $n^{\frac 2 3}$ 项,剩余部分的时间复杂度为 -$O(\int_{0}^{n^{\frac 1 3}} \sqrt{\frac{n}{x}} ~ dx)=O(n^{\frac 2 3})$ + $O(\int_{0}^{n^{\frac 1 3}} \sqrt{\frac{n}{x}} ~ dx)=O(n^{\frac 2 3})$ 对于较大的值,需要用 `map` 存下其对应的值,方便以后使用时直接使用之前计算的结果。 当然也可以用杜教筛求出 $\varphi (x)$ 的前缀和,但是更好的方法是应用莫比乌斯反演: -$\sum_{i=1}^n \sum_{j=1}^n 1[gcd(i,j)=1]=\sum_{i=1}^n \sum_{j=1}^n \sum_{d|i,d|j} \mu(d)$ + $\sum_{i=1}^n \sum_{j=1}^n 1[gcd(i,j)=1]=\sum_{i=1}^n \sum_{j=1}^n \sum_{d|i,d|j} \mu(d)$ -$=\sum_{d=1}^n \mu(d) {\lfloor \frac n d \rfloor}^2$ + $=\sum_{d=1}^n \mu(d) {\lfloor \frac n d \rfloor}^2$ -由于题目所求的是 $\sum_{i=1}^n \sum_{j=1}^i 1[gcd(i,j)=1]$ ,所以我们排除掉 $i=1,j=1$ 的情况,并将结果除以 $2$ 即可。 +由于题目所求的是 $\sum_{i=1}^n \sum_{j=1}^i 1[gcd(i,j)=1]$ ,所以我们排除掉 $i=1,j=1$ 的情况,并将结果除以 $2$ 即可。 -观察到,只需求出莫比乌斯函数的前缀和,就可以快速计算出欧拉函数的前缀和了。时间复杂度 $O(n^{\frac 2 3})$ 。 +观察到,只需求出莫比乌斯函数的前缀和,就可以快速计算出欧拉函数的前缀和了。时间复杂度 $O(n^{\frac 2 3})$ 。 给出一种代码实现: diff --git a/docs/math/euler.md b/docs/math/euler.md index 1993adac..0856b42d 100644 --- a/docs/math/euler.md +++ b/docs/math/euler.md @@ -1,33 +1,33 @@ 欧拉函数是什么? $\varphi(n)$ 表示的是小于等于 $n$ 和 $n$ 互质的数的个数。 -比如说 $\varphi(1) = 1$。 +比如说 $\varphi(1) = 1$ 。 -当 n 是质数的时候,显然有 $\varphi(n) = n - 1$。 +当 n 是质数的时候,显然有 $\varphi(n) = n - 1$ 。 利用唯一分解定理,我们可以把一个整数唯一地分解为质数幂次的乘积, -设 $n = p_1^{k_1}p_2^{k_2} \cdots p_s^{k_s}$,其中 $p_i$ 是质数,那么定义 $\varphi(n) = n \times \prod_{i = 1}^s{\frac{p_i - 1}{p_i}}$ +设 $n = p_1^{k_1}p_2^{k_2} \cdots p_s^{k_s}$ ,其中 $p_i$ 是质数,那么定义 $\varphi(n) = n \times \prod_{i = 1}^s{\frac{p_i - 1}{p_i}}$ ## 欧拉函数的一些神奇性质 - 欧拉函数是积性函数。 - 积性是什么意思呢?如果有 $\gcd(a, b) = 1$,那么 $\varphi(a \times b) = \varphi(a) \times \varphi(b)$。 + 积性是什么意思呢?如果有 $\gcd(a, b) = 1$ ,那么 $\varphi(a \times b) = \varphi(a) \times \varphi(b)$ 。 - 特别地,当 $n$ 是奇数时 $\varphi(2n) = 2 \times \varphi(n)$。 + 特别地,当 $n$ 是奇数时 $\varphi(2n) = 2 \times \varphi(n)$ 。 -- $n = \sum_{d | n}{\varphi(d)}$ +- $n = \sum_{d | n}{\varphi(d)}$ - 利用 [莫比乌斯反演](/math/mobius/) 相关知识可以得出。 + 利用[莫比乌斯反演](/math/mobius/)相关知识可以得出。 - 也可以这样考虑:如果 $\gcd(k, n) = d$,那么 $\gcd(\frac{k}{d},\frac{n}{d}) = 1$。($k < n$) + 也可以这样考虑:如果 $\gcd(k, n) = d$ ,那么 $\gcd(\frac{k}{d},\frac{n}{d}) = 1$ 。( $k < n$ ) - 如果我们设 $f(x)$ 表示 $\gcd(k, n) = x$ 的数的个数,那么 $n = \sum_{i = 1}^n{f(x)}$。 + 如果我们设 $f(x)$ 表示 $\gcd(k, n) = x$ 的数的个数,那么 $n = \sum_{i = 1}^n{f(x)}$ 。 - 根据上面的证明,我们发现,$f(x) = \varphi(\frac{n}{x})$,从而 $n = \sum_{d | n}\varphi(\frac{n}{d})$。注意到约数 $d$ 和 $\frac{n}{d}$ 具有对称性,所以上式化为 $n = \sum_{d | n}\varphi(d)$。 + 根据上面的证明,我们发现, $f(x) = \varphi(\frac{n}{x})$ ,从而 $n = \sum_{d | n}\varphi(\frac{n}{d})$ 。注意到约数 $d$ 和 $\frac{n}{d}$ 具有对称性,所以上式化为 $n = \sum_{d | n}\varphi(d)$ 。 -- 若 $n = p^k$,其中 $p$ 是质数,那么 $\varphi(n) = p^k - p^{k - 1}$。 - (根据定义可知) +- 若 $n = p^k$ ,其中 $p$ 是质数,那么 $\varphi(n) = p^k - p^{k - 1}$ 。 + (根据定义可知) ## 如何求欧拉函数值 diff --git a/docs/math/expectation.md b/docs/math/expectation.md index 4644affb..6bdf49ea 100644 --- a/docs/math/expectation.md +++ b/docs/math/expectation.md @@ -4,35 +4,35 @@ 在一次随机试验中可能发生的不能再细分的结果被称为单位事件,用 $E$ 表示。在随机试验中可能发生的所有单位事件的集合称为事件空间,用 $S$ 来表示。例如在一次掷骰子的随机试验中,如果用获得的点数来表示单位事件,那么一共可能出现 $6$ 个单位事件,则事件空间可以表示为 $S=\{1,2,3,4,5,6\}$ 。 -随机事件是事件空间 $S$ 的子集,它由事件空间 $S$ 中的单位元素构成,用大写字母 $A, B, C,\ldots$ 表示。例如在掷两个骰子的随机试验中,设随机事件 $A$ 为 “获得的点数和大于 $10$ ” ,则 $A$ 可以由下面 $3$ 个单位事件组成: $A = \{ (5,6),(6,5),(6,6)\}$ 。 +随机事件是事件空间 $S$ 的子集,它由事件空间 $S$ 中的单位元素构成,用大写字母 $A, B, C,\ldots$ 表示。例如在掷两个骰子的随机试验中,设随机事件 $A$ 为“获得的点数和大于 $10$ ”,则 $A$ 可以由下面 $3$ 个单位事件组成: $A = \{ (5,6),(6,5),(6,6)\}$ 。 ### 事件的计算 因为事件在一定程度上是以集合的含义定义的,因此可以把集合计算方法直接应用于事件的计算,也就是说,在计算过程中,可以把事件当作集合来对待。 -** 和事件 ** :相当于 ** 并集 ** 。只需其中之一发生,就发生了。 +**和事件**:相当于**并集**。只需其中之一发生,就发生了。 -** 积事件 ** :相当于 ** 交集 ** 。必须要全都发生,才计算概率。 +**积事件**:相当于**交集**。必须要全都发生,才计算概率。 ## 概率 ### 定义 -如果在相同条件下,进行了 n 次试验,事件 A 发生了$N_A$ 次,那么$\frac{N_A}{n}$ 称为事件 A 发生的概率。 +如果在相同条件下,进行了 n 次试验,事件 A 发生了 $N_A$ 次,那么 $\frac{N_A}{n}$ 称为事件 A 发生的概率。 ### 公理 -** 非负性 ** :对于一个事件 $A$ ,有概率 $P(A)\in [0,1]$ 。 +**非负性**:对于一个事件 $A$ ,有概率 $P(A)\in [0,1]$ 。 -** 规范性 ** :事件空间的概率值为 $1$,$P(S)=1$. +**规范性**:事件空间的概率值为 $1$ , $P(S)=1$ . -** 容斥性 ** :若 $P(A+B) = P(A)+P(B)$ ,则 $A$ 和 $B$ 互为独立事件。 +**容斥性**:若 $P(A+B) = P(A)+P(B)$ ,则 $A$ 和 $B$ 互为独立事件。 ### 计算 -** 全概率公式 ** :若事件 $A_1,A_2,\ldots,A_n$ 构成一个完备的事件且都有正概率,即 $\forall i,j, A_i\cap A_j=\varnothing$ 且 $\displaystyle \sum_{i=1}^n A_i=1$,有 $\displaystyle P(B)=\sum_{i=1}^n P(A_i)P(B|A_i)$ 。 +**全概率公式**:若事件 $A_1,A_2,\ldots,A_n$ 构成一个完备的事件且都有正概率,即 $\forall i,j, A_i\cap A_j=\varnothing$ 且 $\displaystyle \sum_{i=1}^n A_i=1$ ,有 $\displaystyle P(B)=\sum_{i=1}^n P(A_i)P(B|A_i)$ 。 -** 贝叶斯定理 ** : $\displaystyle P(B_i|A)=\frac{P(B_i)P(A|B_i)}{\displaystyle \sum_{j=1}^n P(B_j)P(A|B_j)}$ +**贝叶斯定理**: $\displaystyle P(B_i|A)=\frac{P(B_i)P(A|B_i)}{\displaystyle \sum_{j=1}^n P(B_j)P(A|B_j)}$ 公式中,事件 $B_i$ 的概率为 $P(B_i)$ ,事件 $B_i$ 已发生条件下事件 $A$ 的概率为 $P(A|B_i)$ ,事件 $A$ 发生条件下事件 $B_i$ 的概率为 $P(B_i|A)$ 。 @@ -44,12 +44,12 @@ ### 性质 -** 全期望公式 ** : $E(Y)=E[E(Y|X)]$ 。可由全概率公式证明。 +**全期望公式**: $E(Y)=E[E(Y|X)]$ 。可由全概率公式证明。 -** 线性性质 ** : 对于任意两个随机事件 $x,y$ ( ** 不要求相互独立 ** ) ,有 $E(X+Y)=E(X)+E(Y)$ 。 +**线性性质**: 对于任意两个随机事件 $x,y$ (**不要求相互独立**),有 $E(X+Y)=E(X)+E(Y)$ 。 ## 例题 [NOIP2017 初赛 T14, T15](https://ti.luogu.com.cn/problemset/1022) -[NOIP2016 换教室](https://www.luogu.org/problemnew/show/P1850) (概率期望 DP) +[NOIP2016 换教室](https://www.luogu.org/problemnew/show/P1850)(概率期望 DP) diff --git a/docs/math/fermat.md b/docs/math/fermat.md index 7be059d0..783c2c0c 100644 --- a/docs/math/fermat.md +++ b/docs/math/fermat.md @@ -1,18 +1,18 @@ ## 费马小定理 -若 $p$ 为素数,$\gcd(a, p) = 1$,则 $a^{p - 1} \equiv 1 \pmod{p}$。 +若 $p$ 为素数, $\gcd(a, p) = 1$ ,则 $a^{p - 1} \equiv 1 \pmod{p}$ 。 -另一个形式:对于任意整数 $a$,有 $a^p \equiv a \pmod{p}$。 +另一个形式:对于任意整数 $a$ ,有 $a^p \equiv a \pmod{p}$ 。 ## 欧拉定理 -若 $\gcd(a, m) = 1$,则 $a^{\phi(m)} \equiv 1 \pmod{m}$。 +若 $\gcd(a, m) = 1$ ,则 $a^{\phi(m)} \equiv 1 \pmod{m}$ 。 ### 证明 -设 $r_1, r_2, \cdots, r_{\phi(m)}$ 为模 $m$ 意义下的一个简化剩余系,则 $ar_1, ar_2, \cdots, ar_{\phi(m)}$ 也为模 $m$ 意义下的一个简化剩余系。所以 $r_1r_2 \cdots r_{\phi(m)} \equiv ar_1 \cdot ar_2 \cdots ar_{\phi(m)} \equiv a^{\phi(m)}r_1r_2 \cdots r_{\phi(m)} \pmod{m}$,可约去 $r_1r_2 \cdots r_{\phi(m)}$,即得 $a^{\phi(m)} \equiv 1 \pmod{m}$。 +设 $r_1, r_2, \cdots, r_{\phi(m)}$ 为模 $m$ 意义下的一个简化剩余系,则 $ar_1, ar_2, \cdots, ar_{\phi(m)}$ 也为模 $m$ 意义下的一个简化剩余系。所以 $r_1r_2 \cdots r_{\phi(m)} \equiv ar_1 \cdot ar_2 \cdots ar_{\phi(m)} \equiv a^{\phi(m)}r_1r_2 \cdots r_{\phi(m)} \pmod{m}$ ,可约去 $r_1r_2 \cdots r_{\phi(m)}$ ,即得 $a^{\phi(m)} \equiv 1 \pmod{m}$ 。 -当 $m$ 为素数时,由于 $\phi(m) = m - 1$,代入欧拉定理可立即得到费马小定理。 +当 $m$ 为素数时,由于 $\phi(m) = m - 1$ ,代入欧拉定理可立即得到费马小定理。 ## 扩展欧拉定理 @@ -28,27 +28,27 @@ $$ ### 证明 -证明转载自 [synapse7](http://blog.csdn.net/synapse7/article/details/19610361) +证明转载自[synapse7](http://blog.csdn.net/synapse7/article/details/19610361) -1. 在 $a$ 的 $0$ 次,$1$ 次,...,$b$ 次幂模 $m$ 的序列中,前 $r$ 个数($a^0$ 到 $a^{r-1}$) 互不相同,从第 $r$ 个数开始,每 $s$ 个数就循环一次。 +1. 在 $a$ 的 $0$ 次, $1$ 次,。.., $b$ 次幂模 $m$ 的序列中,前 $r$ 个数( $a^0$ 到 $a^{r-1}$ ) 互不相同,从第 $r$ 个数开始,每 $s$ 个数就循环一次。 证明:由鸽巢定理易证。 - 我们把 $r$ 称为 $a$ 幂次模 $m$ 的循环起始点,$s$ 称为循环长度。(注意:$r$ 可以为 $0$) + 我们把 $r$ 称为 $a$ 幂次模 $m$ 的循环起始点, $s$ 称为循环长度。(注意: $r$ 可以为 $0$ ) - 用公式表述为:$a^r\equiv a^{r+s}\pmod{m}$ + 用公式表述为: $a^r\equiv a^{r+s}\pmod{m}$ -2. $a$ 为素数的情况 +2. $a$ 为素数的情况 - 令 $m=p^rm'$,则 $\gcd(p,m')=1$,所以 $p^{\phi(m')}\equiv 1\pmod{m'}$ + 令 $m=p^rm'$ ,则 $\gcd(p,m')=1$ ,所以 $p^{\phi(m')}\equiv 1\pmod{m'}$ - 又由于 $\gcd(p^r,m')=1$,所以 $\phi(m') \mid \varphi(m)$,所以 $p^{\varphi(m)}\equiv 1 \pmod {m'}$,即 $p^\phi(m)=km'+1$,两边同时乘以 $p^r$,得 $p^{r+\phi(m)}=km+p^r$(因为 $m=p^rm'$ ) + 又由于 $\gcd(p^r,m')=1$ ,所以 $\phi(m') \mid \varphi(m)$ ,所以 $p^{\varphi(m)}\equiv 1 \pmod {m'}$ ,即 $p^\phi(m)=km'+1$ ,两边同时乘以 $p^r$ ,得 $p^{r+\phi(m)}=km+p^r$ (因为 $m=p^rm'$ ) - 所以 $p^r\equiv p^{r+s}\pmod m$,这里 $s=\phi(m)$ + 所以 $p^r\equiv p^{r+s}\pmod m$ ,这里 $s=\phi(m)$ -3. 推论:$p^b\equiv p^{r+(b-r) \mod \phi(m)}\pmod m$ +3. 推论: $p^b\equiv p^{r+(b-r) \mod \phi(m)}\pmod m$ -4. 又由于 $m=p^rm'$,所以 $\phi(m) \ge \phi(p^r)=p^{r-1}(p-1) \ge r$ +4. 又由于 $m=p^rm'$ ,所以 $\phi(m) \ge \phi(p^r)=p^{r-1}(p-1) \ge r$ 所以 $p^r\equiv p^{r+\phi(m)}\equiv p^{r \mod \phi(m)+\phi(m)}\pmod m$ @@ -56,20 +56,20 @@ $$ 即 $p^b\equiv p^{b \mod \phi(m)+\phi(m)}\pmod m$ -5. $a$ 为素数的幂的情况 +5. $a$ 为素数的幂的情况 - 是否依然有 $a^{r'}\equiv a^{r'+s'}\pmod m$?(其中 $s'=\phi(m),a=p^k$) + 是否依然有 $a^{r'}\equiv a^{r'+s'}\pmod m$ ?(其中 $s'=\phi(m),a=p^k$ ) - 答案是肯定的,由 2 知 $p^s\equiv 1 \pmod m'$,所以 $p^{s \times \frac{k}{\gcd(s,k)}} \equiv 1\pmod {m'}$,所以当 $s'=\frac{s}{\gcd(s,k)}$ 时才能有 $p^{s'k}\equiv 1\pmod {m'}$ ,此时 $s' \mid s \mid \phi(m)$ ,且 $r'= \lceil \frac{r}{k}\rceil \le r \le \phi(m)$ ,由 $r',s'$ 与 $\phi(m)$ 的关系,依然可以得到 $a^b\equiv a^{b \mod \phi(m)+\phi(m)}\pmod m$ + 答案是肯定的,由 2 知 $p^s\equiv 1 \pmod m'$ ,所以 $p^{s \times \frac{k}{\gcd(s,k)}} \equiv 1\pmod {m'}$ ,所以当 $s'=\frac{s}{\gcd(s,k)}$ 时才能有 $p^{s'k}\equiv 1\pmod {m'}$ ,此时 $s' \mid s \mid \phi(m)$ ,且 $r'= \lceil \frac{r}{k}\rceil \le r \le \phi(m)$ ,由 $r',s'$ 与 $\phi(m)$ 的关系,依然可以得到 $a^b\equiv a^{b \mod \phi(m)+\phi(m)}\pmod m$ -6. $a$ 为合数的情况 +6. $a$ 为合数的情况 只证 $a$ 拆成两个素数的幂的情况,大于两个的用数学归纳法可证。 - 设 $a=a_1a_2,a_i=p_i^{k_i}$,$a_i$ 的循环长度为 $s_i$; + 设 $a=a_1a_2,a_i=p_i^{k_i}$ , $a_i$ 的循环长度为 $s_i$ ; - 则 $s \mid lcm(s_1,s_2)$,由于 $s_1 \mid \phi(m),s_2 \mid \phi(m)$,那么 $lcm(s_1,s_2) \mid \phi(m)$,所以 $s \mid \phi(m)$, $r=\max(\lceil \frac{r_i}{k_i} \rceil) \le \max(r_i) \le \phi(m)$; + 则 $s \mid lcm(s_1,s_2)$ ,由于 $s_1 \mid \phi(m),s_2 \mid \phi(m)$ ,那么 $lcm(s_1,s_2) \mid \phi(m)$ ,所以 $s \mid \phi(m)$ , $r=\max(\lceil \frac{r_i}{k_i} \rceil) \le \max(r_i) \le \phi(m)$ ; - 由 $r,s$ 与 $\phi(m)$ 的关系,依然可以得到 $a^b\equiv a^{b \mod \phi(m)+\phi(m)}\pmod m$; + 由 $r,s$ 与 $\phi(m)$ 的关系,依然可以得到 $a^b\equiv a^{b \mod \phi(m)+\phi(m)}\pmod m$ ; 证毕。 diff --git a/docs/math/fft.md b/docs/math/fft.md index 4e6570ec..10f2bff1 100644 --- a/docs/math/fft.md +++ b/docs/math/fft.md @@ -1,6 +1,6 @@ -(本文转载自 [桃酱的算法笔记](https://zhuanlan.zhihu.com/c_1005817911142838272),原文戳 [链接](https://zhuanlan.zhihu.com/p/41867199),已获得作者授权) +(本文转载自[桃酱的算法笔记](https://zhuanlan.zhihu.com/c_1005817911142838272),原文戳[链接](https://zhuanlan.zhihu.com/p/41867199),已获得作者授权) -一直想学 FFT,之前牛客的多小有一道组合数学就用 FFT 写的,而且当时还傻乎乎的用唯一分解定理,但是自己好久没静下心学什么了,而且自己的数学功底又不好,导致一直学不会。看了很多人的博客也没看明白,尤其是原根。在我看了几十篇博客之后终于看懂了。。。所以想写一篇能够让大多数人都看得懂的教程。花费时间 3 天终于写完啦~~~~\~~ +一直想学 FFT,之前牛客的多小有一道组合数学就用 FFT 写的,而且当时还傻乎乎的用唯一分解定理,但是自己好久没静下心学什么了,而且自己的数学功底又不好,导致一直学不会。看了很多人的博客也没看明白,尤其是原根。在我看了几十篇博客之后终于看懂了……所以想写一篇能够让大多数人都看得懂的教程。花费时间 3 天终于写完啦~~~~\~~ 另外,本文 FFT 部分的代码实现全部参考 kuangbin 的模板(2018.7 更新)资源地址如下 @@ -8,7 +8,7 @@ NTT 部分代码参考 CSDN 上的模板代码附网址,感谢博主! -你搜索这个关键词就已经知道这一是个数学的东西了。只想学会用很简单,但是这远远不够。所以在看这个博客之前应该先学一下 [复数](/math/complex) 的基本知识。 +你搜索这个关键词就已经知道这一是个数学的东西了。只想学会用很简单,但是这远远不够。所以在看这个博客之前应该先学一下[复数](/math/complex)的基本知识。 好了下面进入正文。 @@ -16,9 +16,9 @@ NTT 部分代码参考 CSDN 上的模板代码附网址,感谢博主! > 离散傅里叶变换(Discrete Fourier Transform,缩写为 DFT),是傅里叶变换在时域和频域上都呈离散的形式,将信号的时域采样变换为其 DTFT 的频域采样。 > -> FFT 是一种 DFT 的高效算法,称为快速傅立叶变换(fast Fourier transform)。 ——百度百科 +> FFT 是一种 DFT 的高效算法,称为快速傅立叶变换(fast Fourier transform)。——百度百科 -在百度百科上能找到 DFT 和 FFT 这两个定义。正如定义,FFT 和 DFT 实际上按照结果来看的话是一样的,但是 FFT 比较快的计算 DFT 和IDFT(离散反傅里叶变换)。 +在百度百科上能找到 DFT 和 FFT 这两个定义。正如定义,FFT 和 DFT 实际上按照结果来看的话是一样的,但是 FFT 比较快的计算 DFT 和 IDFT(离散反傅里叶变换)。 快速数论变换 (NTT) 是快速傅里叶变换(FFT)在数论基础上的实现。 @@ -44,7 +44,7 @@ $$ FFT,即为快速傅氏变换,是离散傅氏变换的快速算法,它是根据离散傅氏变换的奇、偶、虚、实等特性,对离散傅立叶变换的算法进行改进获得的。它对傅氏变换的理论并没有新的发现,但是对于在计算机系统或者说数字系统中应用离散傅立叶变换,可以说是进了一大步。——360 百科 -如果上一个例子用朴素算法太慢啦!所以我们要用 FFT 进行优化,复杂度会降为 $O(nlogn)$ +如果上一个例子用朴素算法太慢啦!所以我们要用 FFT 进行优化,复杂度会降为 $O(nlogn)$ ### 多项式的系数表示法与点值表示法 @@ -96,26 +96,25 @@ $$ ### 复数的引入 -复数分为实数和虚数。实数就是我们日常最常用的有理数和无理数。大家记得我们在开始学平方的时候,老师会说所有数的平方大于等于 $0$ 对不对,那么虚数就引入了。虚数一般用 $i$ 表示,对于虚数 $i$,有 $i=\sqrt{-1}$ +复数分为实数和虚数。实数就是我们日常最常用的有理数和无理数。大家记得我们在开始学平方的时候,老师会说所有数的平方大于等于 $0$ 对不对,那么虚数就引入了。虚数一般用 $i$ 表示,对于虚数 $i$ ,有 $i=\sqrt{-1}$ -。另外,$i$ 对于虚数的意义,与 $1$ 对于实数的意义是一样的。如果我说得不够明确,你可以看下面我引用的百科说明。 +。另外, $i$ 对于虚数的意义,与 $1$ 对于实数的意义是一样的。如果我说得不够明确,你可以看下面我引用的百科说明。 -> 在数学中,虚数就是形如 $a+b \times i$ 的数,其中 $a,b$ 是实数,且 $b \neq 0$,$i^2 = - 1$。虚数这个名词是 17 世纪著名数学家笛卡尔创立,因为当时的观念认为这是真实不存在的数字。后来发现虚数 $a+b \times i$ 的实部 $a$ 可对应平面上的横轴,虚部 $b$ 与对应平面上的纵轴,这样虚数 $a+b \times i$ 可与平面内的点 $(a,b)$ 对应。 +> 在数学中,虚数就是形如 $a+b \times i$ 的数,其中 $a,b$ 是实数,且 $b \neq 0$ , $i^2 = - 1$ 。虚数这个名词是 17 世纪著名数学家笛卡尔创立,因为当时的观念认为这是真实不存在的数字。后来发现虚数 $a+b \times i$ 的实部 $a$ 可对应平面上的横轴,虚部 $b$ 与对应平面上的纵轴,这样虚数 $a+b \times i$ 可与平面内的点 $(a,b)$ 对应。 > -> 可以将虚数 $bi$ 添加到实数 $a$ 以形成形式 $a + bi$ 的复数,其中实数 $a$ 和 $b$ 分别被称为复数的实部和虚部。一些作者使用术语纯虚数来表示所谓的虚数,虚数表示具有非零虚部的任何复数。 ——百度百科 +> 可以将虚数 $bi$ 添加到实数 $a$ 以形成形式 $a + bi$ 的复数,其中实数 $a$ 和 $b$ 分别被称为复数的实部和虚部。一些作者使用术语纯虚数来表示所谓的虚数,虚数表示具有非零虚部的任何复数。——百度百科 我们用一幅图来表示复数与复平面的关系(图源百度百科) ![img](./images/fft1.jpg) -其中横坐标是实数轴,纵坐标是虚数轴,这样就可以把每个虚数看为一个向量了,对应的,虚数可以用普通坐标和极坐标 $(r,\theta)$ -(其中 $r$ 为虚数长度,$\theta$ 为虚数和实数轴正半轴夹角) 来表示。 +其中横坐标是实数轴,纵坐标是虚数轴,这样就可以把每个虚数看为一个向量了,对应的,虚数可以用普通坐标和极坐标 $(r,\theta)$ (其中 $r$ 为虚数长度, $\theta$ 为虚数和实数轴正半轴夹角)来表示。 接下来思考两个复数相乘是什么意义: -1. $(a+bi) \times (c+di) = (ac-bd) + (ad+bc)i$ +1. $(a+bi) \times (c+di) = (ac-bd) + (ad+bc)i$ -2. 长度相乘,角度相加: $(r_1, \theta_1) \times (r_2, \theta_2) = (r_1 \times r_2, \theta_1+\theta_2)$ +2. 长度相乘,角度相加: $(r_1, \theta_1) \times (r_2, \theta_2) = (r_1 \times r_2, \theta_1+\theta_2)$ 这么一看的话,我们很容易想到如果两个长度为 $1$ 的不同方向向量相乘,结果向量是不是一个长度依然为 $1$ 的新向量呢? @@ -125,7 +124,7 @@ $$ 考虑这样一个问题: -刚刚说到了 DFT 是把多项式从系数表示转到了点值表示(复杂度为 $O(n)$),那么我们把点值相乘之后(选取相应位置,并且复杂度为 $O(n)$),如果能够快速还原成系数表示,是不是就完美解决我们的问题了呢?上述过程如下: +刚刚说到了 DFT 是把多项式从系数表示转到了点值表示(复杂度为 $O(n)$ ),那么我们把点值相乘之后(选取相应位置,并且复杂度为 $O(n)$ ),如果能够快速还原成系数表示,是不是就完美解决我们的问题了呢?上述过程如下: 假设我们 DFT 过程对于两个多项式选取的 $x$ 序列相同,那么可以得到 @@ -137,7 +136,7 @@ $$ g(x)={(x_0, g(x_0), (x_1, g(x_1)), (x_2, g(x_2), \cdots, (x_n, g(x_n)))} $$ -如果我们设 $F(x) = f(x) \times g(x0$ +如果我们设 $F(x) = f(x) \times g(x0$ 那么很容易得到 $F(x)$ 的点值表达式: @@ -145,25 +144,25 @@ $$ F(x) = {(x_0, f(x_0) \times g(x_0), (x_1, f(x_1) \times g(x_1)), (x_2, f(x_2) \times g(x_2), \cdots, (x_n, f(x_n) \times g(x_n)))} $$ -但是我们要的是系数表达式,接下来问题变成了从点值回到系数。如果我们带入到高斯消元法的方程组中去,会把复杂度变得非常高。光是计算 $x^i(0 \leq i \leq n)$ 就是 $n$ 项, 这就已经 $O(n^2)$ 了,更别说还要把 $n+1$ 个方程进行消元。。。。。。。。。 +但是我们要的是系数表达式,接下来问题变成了从点值回到系数。如果我们带入到高斯消元法的方程组中去,会把复杂度变得非常高。光是计算 $x^i(0 \leq i \leq n)$ 就是 $n$ 项,这就已经 $O(n^2)$ 了,更别说还要把 $n+1$ 个方程进行消元…… -这里会不会觉得我们不去计算 $x^i$ 比较好呢?$1$ 和 $-1$ 的幂都很好算,但是也仅仅有两个不够啊,我们至少需要 $n+1$ 个 o(╥﹏╥)o 那怎么办呢!想到我们刚刚学的长度为 $1$ 的虚数了吗?不管怎么乘长度都是 $1$!对就是它!我们需要的是 $\omega^k=1$ 中的 $\omega$,很容易想到 $-i$ 和 $1$ 是符合的。那其他的呢? +这里会不会觉得我们不去计算 $x^i$ 比较好呢? $1$ 和 $-1$ 的幂都很好算,但是也仅仅有两个不够啊,我们至少需要 $n+1$ 个 o(╥﹏╥)o 那怎么办呢!想到我们刚刚学的长度为 $1$ 的虚数了吗?不管怎么乘长度都是 $1$ !对就是它!我们需要的是 $\omega^k=1$ 中的 $\omega$ ,很容易想到 $-i$ 和 $1$ 是符合的。那其他的呢? ![img](./images/fft2.jpg) -现在我们看上图的圆圈。容易发现这是一个单位圆(圆心为原点,半径为 $1$),所有在圆上的复数的长度均为 $1$,也就是说它不管做多少次方 $r$ 永远为 $1$,结果也仅仅角度的变化而已。但是!!!进过旋转总会让角度 $\bmod 360 = 0$ 成立的,也就是结果为 $1$。 我们把符合以上条件的复数成为复根,用 $\omega$ 表示。如果 $\omega^k=1$ 那么我们把$\omega$ 称为 $1$ 的 $k$ 次复根,记作 $\omega_k^n$ (因为符合这个 $k$ 次之后等于 $1$ 的复数有很多,比如 $i$ 的 $4k$ 次幂永远为 $1$,所以,这个 $n$ 是一个编号,表示这是角度从小到大的第几个(从 $x$ 的正半轴开始逆时针)) +现在我们看上图的圆圈。容易发现这是一个单位圆(圆心为原点,半径为 $1$ ),所有在圆上的复数的长度均为 $1$ ,也就是说它不管做多少次方 $r$ 永远为 $1$ ,结果也仅仅角度的变化而已。但是!进过旋转总会让角度 $\bmod 360 = 0$ 成立的,也就是结果为 $1$ 。我们把符合以上条件的复数成为复根,用 $\omega$ 表示。如果 $\omega^k=1$ 那么我们把 $\omega$ 称为 $1$ 的 $k$ 次复根,记作 $\omega_k^n$ (因为符合这个 $k$ 次之后等于 $1$ 的复数有很多,比如 $i$ 的 $4k$ 次幂永远为 $1$ ,所以,这个 $n$ 是一个编号,表示这是角度从小到大的第几个(从 $x$ 的正半轴开始逆时针)) -是不是有点雾啊 ( ̄▽ ̄)/没事没事接下来我们举个栗子: +是不是有点雾啊 (~▽~)/没事没事接下来我们举个栗子: ![img](./images/fft3.jpg) -那么很容易发现当 $K = 4$ 的时候,相当于把单位圆等分 $K= 4$ 份。然后每一份按照极角编号。那么是不是(在 $K = 4$ 的时候)我们只要知道 $\omega_4^1$ +那么很容易发现当 $K = 4$ 的时候,相当于把单位圆等分 $K= 4$ 份。然后每一份按照极角编号。那么是不是(在 $K = 4$ 的时候)我们只要知道 $\omega_4^1$ -(因为他的角度是相当于单位角度), 就能知道 $\omega_4^0, \omega_4^1, \omega_4^2, \omega_4^3$ 了呢?当然是这样的。。。 +(因为他的角度是相当于单位角度),就能知道 $\omega_4^0, \omega_4^1, \omega_4^2, \omega_4^3$ 了呢?当然是这样的…… -$\omega_4^0$ 恒等于 $1$,$\omega_4^2$ 的角度是 $\omega_4^0$ 的两倍,所以 $\omega_4^2 = (\omega_4^1)^2 = i^2=-1$, 依次以此类推。 + $\omega_4^0$ 恒等于 $1$ , $\omega_4^2$ 的角度是 $\omega_4^0$ 的两倍,所以 $\omega_4^2 = (\omega_4^1)^2 = i^2=-1$ , 依次以此类推。 -因此,我们只要知道 $\omega_k^1$ ,就能求出 $\omega_k^n$。所以我们把 $\omega_k^1$ 称为单位复根,简写为 $\omega_k$ +因此,我们只要知道 $\omega_k^1$ ,就能求出 $\omega_k^n$ 。所以我们把 $\omega_k^1$ 称为单位复根,简写为 $\omega_k$ ## FFT 的流程 @@ -173,7 +172,7 @@ qwq 终于写到核心部分了,也就是,FFT 到底怎么来写呢? FFT 之所以快,是因为他采用了分治的思想。 -就 DFT(将系数表达转换成点值表达)来说,它分治的来求当当前的 $x=\omega_n^k$ +就 DFT(将系数表达转换成点值表达)来说,它分治的来求当当前的 $x=\omega_n^k$ 的时候整个式子的值。他的分治思想体现在将多项式分为奇次项和偶次项处理。 @@ -183,7 +182,7 @@ $$ f(x0) = y_1 = a_0 + a_1x + a_2x^2+a_3x^3+a_4x^4+a_5x^5+a_6x^6+a_7x^7 $$ -按照次数的奇偶来分成两组, 然后右边提出来一个 $x$ +按照次数的奇偶来分成两组,然后右边提出来一个 $x$ $$ f(x) = (a_0+a_2x^2+a_4x^4+a_6x^6) + (a_1x+a_3x^3+a_5x^5+a_7x^7) @@ -203,7 +202,7 @@ $$ H(x)=a_1+a_3x+a_5x^2+a_7x^3 $$ -那么原来的 $f(x)$ 由新函数来表示 (是不是我们二分了一个多项式呢~) +那么原来的 $f(x)$ 由新函数来表示(是不是我们二分了一个多项式呢~) $$ F(x)=G(x^2) + x \times H(x^2) @@ -215,11 +214,11 @@ $$ DFT(f(\omega_n^k))=DFT(G((\omega_n^k)^2)) + \omega_n^k \times DFT(H((\omega_n^k)^2)) $$ -!!!前方高能: +!前方高能: -这个函数能处理的多项式长度只能是 $2^m(m \in N^ \times )$ , 否则在分治的时候左右不一样长,右边取不到系数了,程序没法进行。所以要在第一次 DFT 之前就把序列向上补成长度为 $2^m(m \in N^ \times )$(高次系数补 $0$)、最高项次数为 $n-1$ 的多项式。一定要预处理哦 qaq +这个函数能处理的多项式长度只能是 $2^m(m \in N^ \times )$ , 否则在分治的时候左右不一样长,右边取不到系数了,程序没法进行。所以要在第一次 DFT 之前就把序列向上补成长度为 $2^m(m \in N^ \times )$ (高次系数补 $0$ )、最高项次数为 $n-1$ 的多项式。一定要预处理哦 qaq -然后我在代入值的时候,因为要代入 $n$ 个不同值,所以我们就代入 $\omega_n^0,\omega_n^1,\omega_n^2,\cdots, \omega_n^{n-1} (n=2^m(m \in N^ \times ))$ +然后我在代入值的时候,因为要代入 $n$ 个不同值,所以我们就代入 $\omega_n^0,\omega_n^1,\omega_n^2,\cdots, \omega_n^{n-1} (n=2^m(m \in N^ \times ))$ 一共 $2^m$ 个不同值。 @@ -247,15 +246,15 @@ void fft(Complex y[], int len, int on) { } ``` -但是这个算法还需要从 “分治” 的角度继续优化。我们每一次都会把整个多项式的奇数次项和偶数次项系数分开,一只分到只剩下一个系数。但是,这个递归的过程需要更多的内存。因此,我们可以先 “模仿递归” 把这些系数在原数组中 “拆分”,然后再 “倍增” 地去合并这些算出来的值。然而我们又要如何去拆分这些数呢? +但是这个算法还需要从“分治”的角度继续优化。我们每一次都会把整个多项式的奇数次项和偶数次项系数分开,一只分到只剩下一个系数。但是,这个递归的过程需要更多的内存。因此,我们可以先“模仿递归”把这些系数在原数组中“拆分”,然后再“倍增”地去合并这些算出来的值。然而我们又要如何去拆分这些数呢? -设初始序列为 $\{x_0, x_1, x_2, x_3, x_4, x_5, x_6, x_7\}$ +设初始序列为 $\{x_0, x_1, x_2, x_3, x_4, x_5, x_6, x_7\}$ -一次二分之后 $\{x_0, x_2, x_4, x_6\},\{x_1, x_3,x_5, x_7 \}$ +一次二分之后 $\{x_0, x_2, x_4, x_6\},\{x_1, x_3,x_5, x_7 \}$ -两次二分之后 $\{x_0,x_4\} \{x_2, x_6\},\{x_1, x_3\},\{x_5, x_7 \}$ +两次二分之后 $\{x_0,x_4\} \{x_2, x_6\},\{x_1, x_3\},\{x_5, x_7 \}$ -三次二分之后 $\{x_0\}\{x_4\}\{x_2\}\{x_6\}\{x_1\}\{x_3\}\{x_5\}\{x_7 \}$ +三次二分之后 $\{x_0\}\{x_4\}\{x_2\}\{x_6\}\{x_1\}\{x_3\}\{x_5\}\{x_7 \}$ 有啥规律呢?其实就是原来的那个序列,每个数用二进制表示,然后把二进制翻转对称一下,就是最终那个位置的下标。比如 $x_1$ 是 001,翻转是 100,也就是 4,而且最后那个位置确实是 4,是不是很神奇啊\~\~~ @@ -299,11 +298,11 @@ $$ \begin{bmatrix} a[0] \\ a[1] \\ a[2] \\ a[3] \\ \dots \\ a[n-1] \end{bmatrix} $$ -而且现在我们已经得到最左边的结果了,中间的 $x$ 值在目标多项式的点值表示中也是一一对应的,所以,根据矩阵的基础知识,我们只要在式子两边左乘中间那个大矩阵的逆矩阵就行了。由于这个矩阵的元素非常特殊,他的逆矩阵也有特殊的性质,就是每一项取倒数,再除以 $n$,就能得到他的逆矩阵(这边根据的是单位原根的两个特殊性质推出来的,具体比较麻烦。如果想知道的话私我吧。) +而且现在我们已经得到最左边的结果了,中间的 $x$ 值在目标多项式的点值表示中也是一一对应的,所以,根据矩阵的基础知识,我们只要在式子两边左乘中间那个大矩阵的逆矩阵就行了。由于这个矩阵的元素非常特殊,他的逆矩阵也有特殊的性质,就是每一项取倒数,再除以 $n$ ,就能得到他的逆矩阵(这边根据的是单位原根的两个特殊性质推出来的,具体比较麻烦。如果想知道的话私我吧。) -如何改变我们的操作才能使计算的结果文原来的倒数呢?我们当然可以重新写一遍,但是这里有更简单的实现。这就要看我们求 “单位复根的过程了”:根据 “欧拉函数” $e^{i\pi}=-1$,我么可以得到 $e^{2\pi i}=1$。如果我要找到一个数,它的 $k$ 次方 $= 1$,那么这个数 $\omega[k]=e^{2\pi \frac{i}{k}}$(因为 $(e^{2\pi \frac{i}{k}})^k=e^{2\pi i}=1$)。而如果我要使这个数值变成$\frac{1}{\omega[k]}$ 也就是 $(\omega[k])^-1$,我们可以尝试着把 +如何改变我们的操作才能使计算的结果文原来的倒数呢?我们当然可以重新写一遍,但是这里有更简单的实现。这就要看我们求“单位复根的过程了”:根据“欧拉函数” $e^{i\pi}=-1$ ,我么可以得到 $e^{2\pi i}=1$ 。如果我要找到一个数,它的 $k$ 次方 $= 1$ ,那么这个数 $\omega[k]=e^{2\pi \frac{i}{k}}$ (因为 $(e^{2\pi \frac{i}{k}})^k=e^{2\pi i}=1$ )。而如果我要使这个数值变成 $\frac{1}{\omega[k]}$ 也就是 $(\omega[k])^-1$ ,我们可以尝试着把 -$π$ 取成 - 3.14159…,这样我们的计算结果就会变成原来的倒数,而其它的操作过程与 DFT 是完全相同的(这真是极好的)。我们可以定义一个函数,向里面掺一个参数 $1$ 或者是 $-1$,然后把它乘到 $π$ 的身上。传入 $1$ 就是 DFT,传入 $-1$ 就是 IDFT,十分的智能。 + $π$ 取成 - 3.14159…,这样我们的计算结果就会变成原来的倒数,而其它的操作过程与 DFT 是完全相同的(这真是极好的)。我们可以定义一个函数,向里面掺一个参数 $1$ 或者是 $-1$ ,然后把它乘到 $π$ 的身上。传入 $1$ 就是 DFT,传入 $-1$ 就是 IDFT,十分的智能。 所以我们 fft 函数可以集 DFT 和 IDFT 于一身。见下 @@ -449,8 +448,8 @@ int main() { 但是,算竞选手可能像我一样有下面的疑问: -假如我要计算的多项式系数是别的具有特殊意义的整数,那么我通篇都在用浮点数运算,首先从时间上就会比整数运算慢,另外我最多只能用 long double 不能用 long long 类型,我能不能应用数论的变化从而避开浮点运算,达到 “更高更快更强 (\*・ω<) ” 呢? +假如我要计算的多项式系数是别的具有特殊意义的整数,那么我通篇都在用浮点数运算,首先从时间上就会比整数运算慢,另外我最多只能用 long double 不能用 long long 类型,我能不能应用数论的变化从而避开浮点运算,达到“更高更快更强 (\*・ω<)”呢? ## 算竞选手看过来~ NTT(数论优化的快速傅里叶变换) -戳~ [NTT](/math/ntt) +戳~[NTT](/math/ntt) diff --git a/docs/math/fwt.md b/docs/math/fwt.md index d88575dd..0edb216a 100644 --- a/docs/math/fwt.md +++ b/docs/math/fwt.md @@ -1,54 +1,54 @@ -(本文转载自 [桃酱的算法笔记](https://zhuanlan.zhihu.com/c_1005817911142838272),原文戳 [链接](https://zhuanlan.zhihu.com/p/41867199),已获得作者授权) +(本文转载自[桃酱的算法笔记](https://zhuanlan.zhihu.com/c_1005817911142838272),原文戳[链接](https://zhuanlan.zhihu.com/p/41867199),已获得作者授权) ## 简介 -> 沃尔什转换(Walsh Transform)是在频谱分析上作为离散傅立叶变换的替代方案的一种方法。 —— [维基百科](https://zh.wikipedia.org/zh-cn/%E6%B2%83%E7%88%BE%E4%BB%80%E8%BD%89%E6%8F%9B) +> 沃尔什转换(Walsh Transform)是在频谱分析上作为离散傅立叶变换的替代方案的一种方法。——[维基百科](https://zh.wikipedia.org/zh-cn/%E6%B2%83%E7%88%BE%E4%BB%80%E8%BD%89%E6%8F%9B) 其实这个变换在信号处理中应用很广泛,fft 是 double 类型的,但是 walsh 把信号在不同震荡频率方波下拆解,因此所有的系数都是绝对值大小相同的整数,这使得不需要作浮点数的乘法运算,提高了运算速度。 -所以,FWT 和 FFT 的核心思想应该是相同的。都是对数组的变换。我们设数组 $A$ 经过快速沃尔什变换之后记作 $FWT[A]$. +所以,FWT 和 FFT 的核心思想应该是相同的。都是对数组的变换。我们设数组 $A$ 经过快速沃尔什变换之后记作 $FWT[A]$ . 那么 FWT 核心思想就是: -我们需要一个新序列 $C$,由序列 $A$ 和序列 $B$ 经过某运算规则得到,即 $C = A \cdot B$ +我们需要一个新序列 $C$ ,由序列 $A$ 和序列 $B$ 经过某运算规则得到,即 $C = A \cdot B$ -我们先正向得到 $FWT[A], FWT[B]$ +我们先正向得到 $FWT[A], FWT[B]$ -然后根据 $FWT[C]=FWT[A] \cdot FWT[B]$ +然后根据 $FWT[C]=FWT[A] \cdot FWT[B]$ -在 $O(n)$ 求出 $FWT[C]$ +在 $O(n)$ 求出 $FWT[C]$ -然后逆向想运算得到原序列 $C$。时间复杂度为 $O(nlogn)$ +然后逆向想运算得到原序列 $C$ 。时间复杂度为 $O(nlogn)$ 在算法竞赛中,FWT 是用于解决对下标进行位运算卷积问题的方法。 -公式: $C[i] = \sum_{i=j \bigoplus k}A[j] * B[k]$ +公式: $C[i] = \sum_{i=j \bigoplus k}A[j] * B[k]$ -(其中 $\bigoplus$ 是二元位运算中的某一种,$*$ 是普通乘法) +(其中 $\bigoplus$ 是二元位运算中的某一种, $*$ 是普通乘法) ## FWT 的运算 -### FWT 之与($\And$)运算和或($|$)运算 +### FWT 之与( $\And$ )运算和或( $|$ )运算 与运算和或运算的本质是差不多的,所以这里讲一下或运算,与运算也是可以自己根据公式 yy 出来的。 -### 或运算 $A_i$ +### 或运算 $A_i$ -如果有 $k=i|j$,那么 $i$ 的二进制位为 $1$ 的位置和 $j$ 的二进制位为 $1$ 的位置肯定是 $k$ 的二进制位为 $1$ 的位置的子集。 +如果有 $k=i|j$ ,那么 $i$ 的二进制位为 $1$ 的位置和 $j$ 的二进制位为 $1$ 的位置肯定是 $k$ 的二进制位为 $1$ 的位置的子集。 -现在要得到 $FWT[C] = FWT[A] * FWT[B]$,我们就要构造这个 fwt 的规则。 +现在要得到 $FWT[C] = FWT[A] * FWT[B]$ ,我们就要构造这个 fwt 的规则。 -我们按照定义,显然可以构造 $FWT[A] = A' = \sum_{i=i|j}A[j]$,来表示 $j$ 满足二进制中 $1$ 为 $i$ 的子集。 +我们按照定义,显然可以构造 $FWT[A] = A' = \sum_{i=i|j}A[j]$ ,来表示 $j$ 满足二进制中 $1$ 为 $i$ 的子集。 -那么显然会有 $C[i] = \sum_{i=j|k}A[j]*B[k] \Rightarrow FWT[C] = FWT[A] * FWT[B]$ +那么显然会有 $C[i] = \sum_{i=j|k}A[j]*B[k] \Rightarrow FWT[C] = FWT[A] * FWT[B]$ 那么我们接下来看 $FWT[A]$ 怎么求。 -首先肯定不能枚举了,复杂度为 $O(n^2)$。既然不能整体枚举,我们就考虑分治。 +首先肯定不能枚举了,复杂度为 $O(n^2)$ 。既然不能整体枚举,我们就考虑分治。 我们把整个区间二分,其实二分区间之后,下标写成二进制形式是有规律可循的。 -我们令 $A_0$ 表示 $A$ 的前一半,$A_1$ 表示区间的后一半,那么 $A_0$ 就是 A 下标最大值的最高位为 $0$,他的子集就是他本身的子集(因为最高位为 $0$ 了),但是 $A_1$ 的最高位是 $1$,他满足条件的子集不仅仅是他本身,还包最高位为 $0$ 的子集,即 +我们令 $A_0$ 表示 $A$ 的前一半, $A_1$ 表示区间的后一半,那么 $A_0$ 就是 A 下标最大值的最高位为 $0$ ,他的子集就是他本身的子集(因为最高位为 $0$ 了),但是 $A_1$ 的最高位是 $1$ ,他满足条件的子集不仅仅是他本身,还包最高位为 $0$ 的子集,即 $$ FWT[A] = merge(FWT[A_0], FWT[A_0] + FWT[A_1]) @@ -56,9 +56,9 @@ $$ 其中 merge 表示像字符串拼接一样把它们拼起来, $+$ 就是普通加法,表示对应二进制位相加。 -这样我们就通过二分能在 $O(logn)$ 完成拼接,每次拼接的时候要完成一次运算,也就是说在 $O(nlogn)$ 的时间复杂度得到了 $FWT[A]$。 +这样我们就通过二分能在 $O(logn)$ 完成拼接,每次拼接的时候要完成一次运算,也就是说在 $O(nlogn)$ 的时间复杂度得到了 $FWT[A]$ 。 -接下来就是反演了,其实反演是很简单的,既然知道了 $A_0$ 的本身的子集是他自己 ($A_0 = FAT[A_0]$),$A_1$ 的子集是 $FAT[A_0] + FAT[A_1](A_1'= A_0' + A_1'$), 那就很简单的得出反演的递推式了: +接下来就是反演了,其实反演是很简单的,既然知道了 $A_0$ 的本身的子集是他自己 ( $A_0 = FAT[A_0]$ ), $A_1$ 的子集是 $FAT[A_0] + FAT[A_1](A_1'= A_0' + A_1'$ ),那就很简单的得出反演的递推式了: $$ UFWT[A'] = merge(UFWT[A_0'], UFWT[A_1'] - UFWT[A_0']) @@ -88,7 +88,7 @@ $$ 公式如下: -$A[i] = \sum_{C_1}A[j] - \sum_{C_2}A[j]$ ($C_1$ 表示 $i \And j$ 奇偶性为 $0$,$C_2$ 表示 $i \And j$ 的奇偶性为 $1$) + $A[i] = \sum_{C_1}A[j] - \sum_{C_2}A[j]$ ( $C_1$ 表示 $i \And j$ 奇偶性为 $0$ , $C_2$ 表示 $i \And j$ 的奇偶性为 $1$ ) 结论: @@ -104,7 +104,7 @@ $$ 类比异或运算给出公式: -$A[i] = \sum_{C_1}A[j] - \sum_{C_2}A[j]$ ($C_1$ 表示 $i|j$ 奇偶性为 $0$,$C_2$ 表示 $i|j$ 的奇偶性为 $1$) + $A[i] = \sum_{C_1}A[j] - \sum_{C_2}A[j]$ ( $C_1$ 表示 $i|j$ 奇偶性为 $0$ , $C_2$ 表示 $i|j$ 的奇偶性为 $1$ ) $$ FWT[A] = merge(FWT[A_1] - FWT[A_0], FWT[A_1] + FWT[A_0]) diff --git a/docs/math/game-theory.md b/docs/math/game-theory.md index c7e1bd50..ba113c17 100644 --- a/docs/math/game-theory.md +++ b/docs/math/game-theory.md @@ -1,4 +1,4 @@ -** 博弈论 **,是经济学的一个分支,主要研究具有竞争或对抗性质的对象,在一定规则下产生的各种行为。博弈论考虑游戏中的个体的预测行为和实际行为,并研究它们的优化策略。 +**博弈论**,是经济学的一个分支,主要研究具有竞争或对抗性质的对象,在一定规则下产生的各种行为。博弈论考虑游戏中的个体的预测行为和实际行为,并研究它们的优化策略。 通俗地讲,博弈论主要研究的是:在一个游戏中,进行游戏的多位玩家的策略。 @@ -16,7 +16,7 @@ ## Nim 游戏 -$n$ 堆物品,每堆有 $a_i$ 个,两个玩家轮流取走任意一堆的任意个物品,但不能不取。 + $n$ 堆物品,每堆有 $a_i$ 个,两个玩家轮流取走任意一堆的任意个物品,但不能不取。 取走最后一个物品的人获胜。 @@ -32,7 +32,7 @@ $n$ 堆物品,每堆有 $a_i$ 个,两个玩家轮流取走任意一堆的任 ![博弈图的例子](./images/game1.png) -定义 ** 必胜状态 ** 为 ** 先手必胜的状态 **,** 必败状态 ** 为 ** 先手必败的状态 **。 +定义**必胜状态**为**先手必胜的状态**,**必败状态**为**先手必败的状态**。 通过推理,我们可以得出下面三条定理: @@ -56,7 +56,7 @@ $n$ 堆物品,每堆有 $a_i$ 个,两个玩家轮流取走任意一堆的任 但是,这样的时间复杂度实在太高。有没有什么巧妙而快速的方法呢? -定义 Nim 和 $=a_1 \oplus a_2 \oplus \ldots \oplus a_n$。 +定义 Nim 和 $=a_1 \oplus a_2 \oplus \ldots \oplus a_n$ 。 当且仅当 Nim 和为 $0$ 时,该状态为必败状态;否则该状态为必胜状态。 @@ -98,7 +98,7 @@ $$ SG(x)=mex\{SG(y_1), SG(y_2), \ldots, G(y_k)\} $$ -而对于由 $n$ 个有向图游戏组成的组合游戏,设它们的起点分别为 $s_1, s_2, \ldots, s_n$ ,则有定理:** 当且仅当 $SG(s_1) \oplus SG(s_2) \oplus \ldots \oplus SG(s_n) \neq 0$ 时,这个游戏是先手必胜的。** +而对于由 $n$ 个有向图游戏组成的组合游戏,设它们的起点分别为 $s_1, s_2, \ldots, s_n$ ,则有定理:**当且仅当 $SG(s_1) \oplus SG(s_2) \oplus \ldots \oplus SG(s_n) \neq 0$ 时,这个游戏是先手必胜的。** 这一定理被称作 SG 定理。 @@ -112,6 +112,6 @@ $$ ## 参考文献 -[(转载)Nim 游戏博弈 (收集完全版) - exponent - 博客园](http://www.cnblogs.com/exponent/articles/2141477.html) +[(转载)Nim 游戏博弈(收集完全版)- exponent - 博客园](http://www.cnblogs.com/exponent/articles/2141477.html) -[\[组合游戏与博弈论\]【学习笔记】 - Candy? - 博客园](https://www.cnblogs.com/candy99/p/6548836.html) +[\[组合游戏与博弈论\]【学习笔记】- Candy? - 博客园](https://www.cnblogs.com/candy99/p/6548836.html) diff --git a/docs/math/gauss.md b/docs/math/gauss.md index 56b29318..961749ef 100644 --- a/docs/math/gauss.md +++ b/docs/math/gauss.md @@ -25,15 +25,15 @@ $$ 解:将方程组中两方程相加,消元 $y$ 可得: -$5x = 200$ + $5x = 200$ 解得: -$x = 40$ + $x = 40$ 将 $x = 40$ 代入方程组中第二个方程可得: -$y = -60$ + $y = -60$ #### 消元法理论的核心 @@ -41,7 +41,7 @@ $y = -60$ - 两方程互换,解不变; -- 一方程乘以非零数 $k$,解不变; +- 一方程乘以非零数 $k$ ,解不变; - 一方程乘以数 $k$ 加上另一方程,解不变。 @@ -87,7 +87,7 @@ $$ #### 增广矩阵行(初等)变换为行最简形 -所谓增广矩阵,即为方程组系数矩阵 $A$ 与常数列 $b$ 的并生成的新矩阵,即 $(A | b)$,增广矩阵行初等变换化为行最简形,即是利用了高斯消元法的思想理念,省略了变量而用变量的系数位置表示变量,增广矩阵中用竖线隔开了系数矩阵和常数列,代表了等于符号。 +所谓增广矩阵,即为方程组系数矩阵 $A$ 与常数列 $b$ 的并生成的新矩阵,即 $(A | b)$ ,增广矩阵行初等变换化为行最简形,即是利用了高斯消元法的思想理念,省略了变量而用变量的系数位置表示变量,增广矩阵中用竖线隔开了系数矩阵和常数列,代表了等于符号。 $$ \left(\begin{matrix} @@ -172,7 +172,7 @@ $$ 解释 -> 即是对于所还原的线性方程组而言,将方程组中每个方程的第一个变量,用其他量表达出来。如方程组两方程中的第一个变量 $x_1$ 和 $x_3$ +> 即是对于所还原的线性方程组而言,将方程组中每个方程的第一个变量,用其他量表达出来。如方程组两方程中的第一个变量 $x_1$ 和 $x_3$ #### 补充自由未知量 @@ -187,7 +187,7 @@ $$ 解释 -> 第 3 步中,求解出变量 $x_1$ 和 $x_3$,从而说明了方程剩余的变量 $x_2$ 和 $x_4$ 不受方程组的约束,是自由未知量,可以取任意值,所以需要在第 3 步骤解得基础上进行解得补充,补充的方法为 $x_2 = x_2,x_4 = x_4$,这种解得补充方式符合自由未知量定义,并易于理解,因为是自由未知量而不受约束,所以只能自己等于自己。 +> 第 3 步中,求解出变量 $x_1$ 和 $x_3$ ,从而说明了方程剩余的变量 $x_2$ 和 $x_4$ 不受方程组的约束,是自由未知量,可以取任意值,所以需要在第 3 步骤解得基础上进行解得补充,补充的方法为 $x_2 = x_2,x_4 = x_4$ ,这种解得补充方式符合自由未知量定义,并易于理解,因为是自由未知量而不受约束,所以只能自己等于自己。 #### 列表示方程组的通解 @@ -207,13 +207,13 @@ $$ 解释 -> 即在第 4 步的基础上,将解表达为列向量组合的表示形式,同时由于 $x_2$ 和 $x_4$ 是自由未知量,可以取任意值,所以在解得右边,令二者分别为任意常数 $C_1$ 和 $C_2$,即实现了对方程组的求解。 +> 即在第 4 步的基础上,将解表达为列向量组合的表示形式,同时由于 $x_2$ 和 $x_4$ 是自由未知量,可以取任意值,所以在解得右边,令二者分别为任意常数 $C_1$ 和 $C_2$ ,即实现了对方程组的求解。 * * * ## 行列式 -$N \times N$ 方阵行列式可以理解为所有列向量所夹的几何体的有向体积 + $N \times N$ 方阵行列式可以理解为所有列向量所夹的几何体的有向体积 例如: @@ -235,7 +235,7 @@ $$ D = \left| A \right| = \sum(-1)^va_{1,l_1}a_{2,l_2}\dots a_{n,l_n} $$ -> 其中 $v$ 为 $l_1$, $l_2$, $\cdots$, $l_n$ 中逆序对的个数。 +> 其中 $v$ 为 $l_1$ , $l_2$ , $\cdots$ , $l_n$ 中逆序对的个数。 通过体积概念理解行列式不变性是一个非常简单的办法: diff --git a/docs/math/gcd.md b/docs/math/gcd.md index e01072b2..12d71e3d 100644 --- a/docs/math/gcd.md +++ b/docs/math/gcd.md @@ -2,7 +2,7 @@ 最大公约数即为 Greatest Common Divisor,常缩写为 gcd -在 [素数](/math/prime) 一节中,我们已经介绍了约数的概念。 +在[素数](/math/prime)一节中,我们已经介绍了约数的概念。 一组数的公约数,是指同时是这组数中每一个数的约数的数。而最大公约数,则是指所有公约数里面最大的一个。 @@ -10,35 +10,28 @@ ### 两个数的 -如果我们已知两个数 $a$ 和 $b$,如何求出二者的最大公约数呢? +如果我们已知两个数 $a$ 和 $b$ ,如何求出二者的最大公约数呢? -不妨设 $a > b$ +不妨设 $a > b$ 我们发现如果 $b$ 是 $a$ 的约数,那么 $b$ 就是二者的最大公约数。 -下面讨论不能整除的情况,即 $a = b \times q + r$,其中 $r < b$。 +下面讨论不能整除的情况,即 $a = b \times q + r$ ,其中 $r < b$ 。 -我们通过证明可以得到$\gcd(a,b)=\gcd(b,a \bmod b)$,过程如下: +我们通过证明可以得到 $\gcd(a,b)=\gcd(b,a \bmod b)$ ,过程如下: * * * -设$a=bk+c$,显然有$c=a \bmod b$。设$d|a\ \ \ d|b$,则 -$c=a-bk$ -$\frac{c}{d}=\frac{a}{d}-\frac{b}{d}k$ -由右边的式子可知$\frac{c}{d}$为整数,即$d|c$所以对于$a,b$的公约数,它也会是$a \bmod b$的公约数。 +设 $a=bk+c$ ,显然有 $c=a \bmod b$ 。设 $d|a\ \ \ d|b$ ,则 $c=a-bk$ $\frac{c}{d}=\frac{a}{d}-\frac{b}{d}k$ 由右边的式子可知 $\frac{c}{d}$ 为整数,即 $d|c$ 所以对于 $a,b$ 的公约数,它也会是 $a \bmod b$ 的公约数。 反过来也需要证明 -设$d|b\ \ \ d|(a \bmod b)$,我们还是可以像之前一样得到以下式子 -$\frac{a\bmod b}{d}=\frac{a}{d}-\frac{b}{d}k$ -$\frac{a\bmod b}{d}+\frac{b}{d}k=\frac{a}{d}$ -因为左边式子显然为整数,所以$\frac{a}{d}$也为整数,即$d|a$,所以$b,a\bmod b$的公约数也是$a,b$的公约数。 +设 $d|b\ \ \ d|(a \bmod b)$ ,我们还是可以像之前一样得到以下式子 $\frac{a\bmod b}{d}=\frac{a}{d}-\frac{b}{d}k$ $\frac{a\bmod b}{d}+\frac{b}{d}k=\frac{a}{d}$ 因为左边式子显然为整数,所以 $\frac{a}{d}$ 也为整数,即 $d|a$ ,所以 $b,a\bmod b$ 的公约数也是 $a,b$ 的公约数。 既然两式公约数都是相同的,那么最大公约数也会相同 -所以得到式子 -$\gcd(a,b)=\gcd(b,a\bmod b)$ +所以得到式子 $\gcd(a,b)=\gcd(b,a\bmod b)$ -既然得到了$\gcd(a, b) = \gcd(b, r)$,这里两个数的大小是不会增大的,那么我们也就得到了关于两个数的最大公约数的一个递归求法。 +既然得到了 $\gcd(a, b) = \gcd(b, r)$ ,这里两个数的大小是不会增大的,那么我们也就得到了关于两个数的最大公约数的一个递归求法。 ```cpp int gcd(int a, int b) { @@ -47,9 +40,9 @@ int gcd(int a, int b) { } ``` -递归至`b==0`(即上一步的`a%b==0`) 的情况再返回值即可。 +递归至 `b==0` (即上一步的 `a%b==0` ) 的情况再返回值即可。 -如果两个数 $a$ 和 $b$ 满足 $\gcd(a, b) = 1$,我们称 $a$ 和 $b$ 互质。 +如果两个数 $a$ 和 $b$ 满足 $\gcd(a, b) = 1$ ,我们称 $a$ 和 $b$ 互质。 ### 多个数的 @@ -59,62 +52,57 @@ int gcd(int a, int b) { ### 两个数的 -首先我们介绍这样一个定理 —— 算术基本定理: +首先我们介绍这样一个定理——算术基本定理: -> 每一个正整数都可以表示成若干整数的乘积,这种分解方式在忽略排列次序的条件下是唯一的。 +> 每一个正整数都可以表示成若干整数的乘积,这种分解方式在忽略排列次序的条件下是唯一的。 -用数学公式来表示就是 $x = p_1^{k_1}p_2^{k_2} \cdots p_s^{k_s}$ +用数学公式来表示就是 $x = p_1^{k_1}p_2^{k_2} \cdots p_s^{k_s}$ -设 $a = p_{a_1}^{k_{a_1}}p_{a_2}^{k_{a_2}} \cdots p_{a_s}^{k_{a_s}}$, $b = p_{b_1}^{k_{b_1}}p_{b_2}^{k_{b_2}} \cdots p_{b_s}^{k_{b_s}}$ +设 $a = p_{a_1}^{k_{a_1}}p_{a_2}^{k_{a_2}} \cdots p_{a_s}^{k_{a_s}}$ , $b = p_{b_1}^{k_{b_1}}p_{b_2}^{k_{b_2}} \cdots p_{b_s}^{k_{b_s}}$ 我们发现,对于 $a$ 和 $b$ 的情况,二者的最大公约数等于 -$p_1^{k_{\min(a_1, b_1)}}p_2^{k_{\min(a_2, b_2)}} \cdots p_s^{k_{\min(a_s, b_s)}}$ + $p_1^{k_{\min(a_1, b_1)}}p_2^{k_{\min(a_2, b_2)}} \cdots p_s^{k_{\min(a_s, b_s)}}$ 最小公倍数等于 -$p_1^{k_{\max(a_1, b_1)}}p_2^{k_{\max(a_2, b_2)}} \cdots p_s^{k_{\max(a_s, b_s)}}$ + $p_1^{k_{\max(a_1, b_1)}}p_2^{k_{\max(a_2, b_2)}} \cdots p_s^{k_{\max(a_s, b_s)}}$ -由于 $a + b = \max(a, b) + \min(a, b)$ +由于 $a + b = \max(a, b) + \min(a, b)$ -所以得到结论是 $\gcd(a, b) \times \operatorname{lcm}(a, b) = a \times b$ +所以得到结论是 $\gcd(a, b) \times \operatorname{lcm}(a, b) = a \times b$ 要求两个数的最小公倍数,先求出最大公约数即可。 ### 多个数的 -可以发现,当我们求出两个数的$gcd$时,求最小公倍数是$O(1)$的复杂度。那么对于多个数,我们其实没有必要求一个共同的最大公约数再去处理,最直接的方法就是,当我们算出两个数的$gcd$,或许在求多个数的$gcd$时候,我们将它放入序列对后面的数继续求解,那么,我们转换一下,直接将最小公倍数放入序列即可 +可以发现,当我们求出两个数的 $gcd$ 时,求最小公倍数是 $O(1)$ 的复杂度。那么对于多个数,我们其实没有必要求一个共同的最大公约数再去处理,最直接的方法就是,当我们算出两个数的 $gcd$ ,或许在求多个数的 $gcd$ 时候,我们将它放入序列对后面的数继续求解,那么,我们转换一下,直接将最小公倍数放入序列即可 ## EXGCD - 扩展欧几里得定理 -目的:求$ax+by=\gcd(a,b)$的一组可行解 +目的:求 $ax+by=\gcd(a,b)$ 的一组可行解 ## 证明 设 -$ax_1+by_1=\gcd(a,b)$ + $ax_1+by_1=\gcd(a,b)$ -$bx_2+(a\bmod b)y_2=\gcd(b,a\bmod b)$ + $bx_2+(a\bmod b)y_2=\gcd(b,a\bmod b)$ -由欧几里得定理可知: -$\gcd(a,b)=\gcd(b,a\bmod b)$ +由欧几里得定理可知: $\gcd(a,b)=\gcd(b,a\bmod b)$ -所以 -$ax_1+by_1=bx_2+(a\bmod b)y_2$ +所以 $ax_1+by_1=bx_2+(a\bmod b)y_2$ -又因为 -$a\bmod b=a-(\lfloor\frac{a}{b}\rfloor\times b)$ +又因为 $a\bmod b=a-(\lfloor\frac{a}{b}\rfloor\times b)$ -所以 -$ax_1+by_1=bx_2+(a-(\lfloor\frac{a}{b}\rfloor\times b))y_2$ +所以 $ax_1+by_1=bx_2+(a-(\lfloor\frac{a}{b}\rfloor\times b))y_2$ -$ax_1+by_1=ay_2+bx_2-\lfloor\frac{a}{b}\rfloor\times by_2=ay_2+b(x_2-\lfloor\frac{a}{b}\rfloor y_2)$ + $ax_1+by_1=ay_2+bx_2-\lfloor\frac{a}{b}\rfloor\times by_2=ay_2+b(x_2-\lfloor\frac{a}{b}\rfloor y_2)$ -因为 $a=a,b=b$ ,所以 -$x_1=y_2,y_1=x_2-\lfloor\frac{a}{b}\rfloor y_2$ +因为 $a=a,b=b$ ,所以 $x_1=y_2,y_1=x_2-\lfloor\frac{a}{b}\rfloor y_2$ -将 $x_2,y_2$ 不断代入递归求解直至 GCD(最大公约数,下同) 为 `0` 递归 `x=1,y=0` 回去求解。 +将 $x_2,y_2$ 不断代入递归求解直至 GCD(最大公约数,下同)为 `0` 递归 `x=1,y=0` 回去求解。 ```cpp int Exgcd(int a, int b, int &x, int &y) { diff --git a/docs/math/inclusion-exclusion-principle.md b/docs/math/inclusion-exclusion-principle.md index a8732cfb..b57ad946 100644 --- a/docs/math/inclusion-exclusion-principle.md +++ b/docs/math/inclusion-exclusion-principle.md @@ -1,25 +1,25 @@ -假设班里有 $10$ 个学生喜欢数学, $15$ 个学生喜欢语文, $21$ 个学生喜欢编程,班里至少喜欢一门学科的有多少个学生呢?是 $10+15+21=46$ 个吗?不是的,因为有些学生可能同时喜欢数学和语文,或者语文和编程,甚至还有可能三者都喜欢。为了叙述方便,我们把喜欢语文、数学、编程的学生集合分别用 $A,B,C$ 表示,则学生总数等于 $|A\cup B\cup C|$。刚才已经讲过,如果把这三个集合的元素个数 $|A|,|B|,|C|$ 直接加起来,会有一些元素重复统计了,因此需要扣掉 $|A\cap B|,|B\cap C|,|C\cap A|$ ,但这样一来,又有一小部分多扣了,需要加回来,即 $|A\cap B\cap C|$ 。即 +假设班里有 $10$ 个学生喜欢数学, $15$ 个学生喜欢语文, $21$ 个学生喜欢编程,班里至少喜欢一门学科的有多少个学生呢?是 $10+15+21=46$ 个吗?不是的,因为有些学生可能同时喜欢数学和语文,或者语文和编程,甚至还有可能三者都喜欢。为了叙述方便,我们把喜欢语文、数学、编程的学生集合分别用 $A,B,C$ 表示,则学生总数等于 $|A\cup B\cup C|$ 。刚才已经讲过,如果把这三个集合的元素个数 $|A|,|B|,|C|$ 直接加起来,会有一些元素重复统计了,因此需要扣掉 $|A\cap B|,|B\cap C|,|C\cap A|$ ,但这样一来,又有一小部分多扣了,需要加回来,即 $|A\cap B\cap C|$ 。即 -$|A\cup B\cup C|=|A|+|B|+|C|-|A\cap B|-|B\cap C|-|C\cap A|+|A\cap B\cap C|$ + $|A\cup B\cup C|=|A|+|B|+|C|-|A\cap B|-|B\cap C|-|C\cap A|+|A\cap B\cap C|$ 一般地,对于任意多个集合,我们都可以列出这样一个等式,等式左边是所有集合的并的元素个数,右边是这些集合的各种搭配。每个搭配都是若干个集合的交集,且每一项前面的正负号取决于集合的个数————奇数个集合为正,偶数个集合为负。即 设 $S$ 为有限集, $A_i\in S~(i=1,2,...,n~,~n\ge 2)$ ,则有 -$| \bigcup_{i=1}^n A_i | =\sum_{k=1}^n (-1)^{(k-1)} \times \sum_{1\le i_1 若 $p$ 为质数,$a$ 为正整数,且 $a$、$p$ 互质,则 $a^{p-1} \equiv 1 \pmod p$。 +> 若 $p$ 为质数, $a$ 为正整数,且 $a$ 、 $p$ 互质,则 $a^{p-1} \equiv 1 \pmod p$ 。 -因为 $ax \equiv 1 \pmod b$; +因为 $ax \equiv 1 \pmod b$ ; -所以 $ax \equiv a^{b-1} \pmod b$(根据费马小定理); +所以 $ax \equiv a^{b-1} \pmod b$ (根据费马小定理); -所以 $x \equiv a^{b-2} \pmod b$; +所以 $x \equiv a^{b-2} \pmod b$ ; 然后我们就可以用快速幂来求了。 @@ -56,17 +56,17 @@ inline ll poW(ll a, ll b) { 但是如果要求的很多,以上两种方法就显得慢了,很有可能超时,所以下面来讲一下如何线性求逆元。 -首先,很显然的 $1^{-1} \equiv 1 \pmod p$; +首先,很显然的 $1^{-1} \equiv 1 \pmod p$ ; -然后,设 $p=ki+j,j 方程 $ax+by=c$ 与方程 $ax \equiv c \pmod b$ 是等价的,有整数解的充要条件为 $\gcd(a,b) \mid c$。 +> 方程 $ax+by=c$ 与方程 $ax \equiv c \pmod b$ 是等价的,有整数解的充要条件为 $\gcd(a,b) \mid c$ 。 -根据定理 1,方程 $ax+by=c$,我们可以先用扩展欧几里得算法求出一组 $x_0,y_0$,也就是 $ax_0+by_0=\gcd(a,b)$,然后两边同时除以 $\gcd(a,b)$,再乘 $c$。然后就得到了方程 $acx_0/\gcd(a,b)+bcy_0/\gcd(a,b)=c$,然后我们就找到了方程的一个解。 +根据定理 1,方程 $ax+by=c$ ,我们可以先用扩展欧几里得算法求出一组 $x_0,y_0$ ,也就是 $ax_0+by_0=\gcd(a,b)$ ,然后两边同时除以 $\gcd(a,b)$ ,再乘 $c$ 。然后就得到了方程 $acx_0/\gcd(a,b)+bcy_0/\gcd(a,b)=c$ ,然后我们就找到了方程的一个解。 定理 2: -> 若 $\gcd(a,b)=1$,且 $x_0,y_0$ 为方程 $ax+by=c$ 的一组解,则该方程的任意解可表示为:$x=x_0+bt,y=y_0+at$, 且对任意整数 $t$ 都成立。 +> 若 $\gcd(a,b)=1$ ,且 $x_0,y_0$ 为方程 $ax+by=c$ 的一组解,则该方程的任意解可表示为: $x=x_0+bt,y=y_0+at$ , 且对任意整数 $t$ 都成立。 -根据定理 2,可以求出方程的所有解。但在实际问题中,我们往往被要求求出一个最小整数解,也就是一个特解 $x,t=b/\gcd(a,b),x=(x \mod t+t)\mod t$。 +根据定理 2,可以求出方程的所有解。但在实际问题中,我们往往被要求求出一个最小整数解,也就是一个特解 $x,t=b/\gcd(a,b),x=(x \mod t+t)\mod t$ 。 代码: diff --git a/docs/math/matrix.md b/docs/math/matrix.md index 3748eb95..30c3236b 100644 --- a/docs/math/matrix.md +++ b/docs/math/matrix.md @@ -1,6 +1,6 @@ ## 定义 -对于矩阵 $A$,主对角线是指 $A[i][i]$ 的元素。 +对于矩阵 $A$ ,主对角线是指 $A[i][i]$ 的元素。 一般用 $I$ 来表示单位矩阵,就是主对角线上为 1,其余位置为 0。 @@ -8,9 +8,9 @@ ### 矩阵的逆 -$A$ 的逆矩阵 $P$ 是使得 $A \times P = I$ 的矩阵。 + $A$ 的逆矩阵 $P$ 是使得 $A \times P = I$ 的矩阵。 -逆矩阵可以用 [高斯消元](/math/gauss/) 的方式来求。 +逆矩阵可以用[高斯消元](/math/gauss/)的方式来求。 ## 运算 @@ -20,7 +20,7 @@ $A$ 的逆矩阵 $P$ 是使得 $A \times P = I$ 的矩阵。 矩阵相乘只有在第一个矩阵的列数和第二个矩阵的行数相同时才有意义。 -设 $A$ 为 $P \times M$ 的矩阵,$B$ 为 $M \times Q$ 的矩阵,设矩阵 $C$ 为矩阵 $A$ 与 $B$ 的乘积, +设 $A$ 为 $P \times M$ 的矩阵, $B$ 为 $M \times Q$ 的矩阵,设矩阵 $C$ 为矩阵 $A$ 与 $B$ 的乘积, 其中矩阵 $C$ 中的第 $i$ 行第 $j$ 列元素可以表示为: @@ -32,7 +32,7 @@ $$ 矩阵乘法满足结合律,不满足一般的交换律。 -利用结合律,矩阵乘法可以利用 [快速幂](/math/quick-pow/) 的思想来优化。 +利用结合律,矩阵乘法可以利用[快速幂](/math/quick-pow/)的思想来优化。 在比赛中,由于线性递推式可以表示成矩阵乘法的形式,也通常用矩阵快速幂来求线性递推数列的某一项。 @@ -86,25 +86,25 @@ struct mat { ### 矩阵加速递推 -斐波那契数列(Fibonacci Sequence)大家应该都非常的熟悉了。在斐波那契数列当中,$F_1 = F_2 = 1$,$F_i = F_{i - 1} + F_{i - 2}(i \geq 3)$。 +斐波那契数列(Fibonacci Sequence)大家应该都非常的熟悉了。在斐波那契数列当中, $F_1 = F_2 = 1$ , $F_i = F_{i - 1} + F_{i - 2}(i \geq 3)$ 。 如果有一道题目让你求斐波那契数列第 $n$ 项的值,最简单的方法莫过于直接递推了。但是如果 $n$ 的范围达到了 $10^{18}$ 级别,递推就不行了,稳 TLE。考虑矩阵加速递推。 -设 $Fib(n)$ 表示一个 $1 \times 2$ 的矩阵 $\left[ \begin{array}{ccc}F_n & F_{n-1} \end{array}\right]$。我们希望根据 $Fib(n-1)=\left[ \begin{array}{ccc}F_{n-1} & F_{n-2} \end{array}\right]$ 推出 $Fib(n)$。 +设 $Fib(n)$ 表示一个 $1 \times 2$ 的矩阵 $\left[ \begin{array}{ccc}F_n & F_{n-1} \end{array}\right]$ 。我们希望根据 $Fib(n-1)=\left[ \begin{array}{ccc}F_{n-1} & F_{n-2} \end{array}\right]$ 推出 $Fib(n)$ 。 -试推导一个矩阵 $\text{base}$,使 $Fib(n-1) \times \text{base} = Fib(n)$,即 $\left[\begin{array}{ccc}F_{n-1} & F_{n-2}\end{array}\right] \times \text{base} = \left[ \begin{array}{ccc}F_n & F_{n-1} \end{array}\right]$。 +试推导一个矩阵 $\text{base}$ ,使 $Fib(n-1) \times \text{base} = Fib(n)$ ,即 $\left[\begin{array}{ccc}F_{n-1} & F_{n-2}\end{array}\right] \times \text{base} = \left[ \begin{array}{ccc}F_n & F_{n-1} \end{array}\right]$ 。 -怎么推呢?因为 $F_n=F_{n-1}+F_{n-2}$,所以 $\text{base}$ 矩阵第一列应该是 $\left[\begin{array}{ccc} 1 \\ 1 \end{array}\right]$,这样在进行矩阵乘法运算的时候才能令 $F_{n-1}$ 与 $F_{n-2}$ 相加,从而得出 $F_n$。同理,为了得出 $F_{n-1}$,矩阵 $\text{base}$ 的第二列应该为 $\left[\begin{array}{ccc} 1 \\ 0 \end{array}\right]$。 +怎么推呢?因为 $F_n=F_{n-1}+F_{n-2}$ ,所以 $\text{base}$ 矩阵第一列应该是 $\left[\begin{array}{ccc} 1 \\ 1 \end{array}\right]$ ,这样在进行矩阵乘法运算的时候才能令 $F_{n-1}$ 与 $F_{n-2}$ 相加,从而得出 $F_n$ 。同理,为了得出 $F_{n-1}$ ,矩阵 $\text{base}$ 的第二列应该为 $\left[\begin{array}{ccc} 1 \\ 0 \end{array}\right]$ 。 -综上所述:$\text{base} = \left[\begin{array}{ccc} 1 & 1 \\ 1 & 0 \end{array}\right]$ 原式化为 $\left[\begin{array}{ccc}F_{n-1} & F_{n-2}\end{array}\right] \times \left[\begin{array}{ccc} 1 & 1 \\ 1 & 0 \end{array}\right] = \left[ \begin{array}{ccc}F_n & F_{n-1} \end{array}\right]$ +综上所述: $\text{base} = \left[\begin{array}{ccc} 1 & 1 \\ 1 & 0 \end{array}\right]$ 原式化为 $\left[\begin{array}{ccc}F_{n-1} & F_{n-2}\end{array}\right] \times \left[\begin{array}{ccc} 1 & 1 \\ 1 & 0 \end{array}\right] = \left[ \begin{array}{ccc}F_n & F_{n-1} \end{array}\right]$ 转化为代码,应该怎么求呢? -定义初始矩阵 $\text{ans} = \left[\begin{array}{ccc}F_2 & F_1\end{array}\right] = \left[\begin{array}{ccc}1 & 1\end{array}\right], \text{base} = \left[\begin{array}{ccc} 1 & 1 \\ 1 & 0 \end{array}\right]$。那么,$F_n$ 就等于 $\text{ans} \times \text{base}^{n-2}$ 这个矩阵的第一行第一列元素,也就是 $\left[\begin{array}{ccc}1 & 1\end{array}\right] \times \left[\begin{array}{ccc} 1 & 1 \\ 1 & 0 \end{array}\right]^{n-2}$ 的第一行第一列元素。 +定义初始矩阵 $\text{ans} = \left[\begin{array}{ccc}F_2 & F_1\end{array}\right] = \left[\begin{array}{ccc}1 & 1\end{array}\right], \text{base} = \left[\begin{array}{ccc} 1 & 1 \\ 1 & 0 \end{array}\right]$ 。那么, $F_n$ 就等于 $\text{ans} \times \text{base}^{n-2}$ 这个矩阵的第一行第一列元素,也就是 $\left[\begin{array}{ccc}1 & 1\end{array}\right] \times \left[\begin{array}{ccc} 1 & 1 \\ 1 & 0 \end{array}\right]^{n-2}$ 的第一行第一列元素。 注意,矩阵乘法不满足交换律,所以一定不能写成 $\left[\begin{array}{ccc} 1 & 1 \\ 1 & 0 \end{array}\right]^{n-2} \times \left[\begin{array}{ccc}1 & 1\end{array}\right]$ 的第一行第一列元素。另外,对于 $n \leq 2$ 的情况,直接输出 $1$ 即可,不需要执行矩阵快速幂。 -为什么要乘上 $\text{base}$ 矩阵的 $n-2$ 次方而不是 $n$ 次方呢?因为 $F_1, F_2$ 是不需要进行矩阵乘法就能求的。也就是说,如果只进行一次乘法,就已经求出 $F_3$ 了。如果还不是很理解为什么幂是 $n-2$,建议手算一下。 +为什么要乘上 $\text{base}$ 矩阵的 $n-2$ 次方而不是 $n$ 次方呢?因为 $F_1, F_2$ 是不需要进行矩阵乘法就能求的。也就是说,如果只进行一次乘法,就已经求出 $F_3$ 了。如果还不是很理解为什么幂是 $n-2$ ,建议手算一下。 下面是求斐波那契数列第 $n$ 项对 $10^9+7$ 取模的示例代码(核心部分)。 @@ -149,5 +149,5 @@ int main() { #### 习题 - [洛谷 P1962 斐波那契数列](https://www.luogu.org/problemnew/show/P1962),即上面的例题,同题 POJ3070 -- [洛谷 P1349 广义斐波那契数列](https://www.luogu.org/problemnew/show/P1349),$\text{base}$ 矩阵需要变化一下 -- [洛谷 P1939 【模板】矩阵加速(数列)](https://www.luogu.org/problemnew/show/P1939),$\text{base}$ 矩阵变成了 $3 \times 3$ 的矩阵,推导过程与上面差不多。 +- [洛谷 P1349 广义斐波那契数列](https://www.luogu.org/problemnew/show/P1349), $\text{base}$ 矩阵需要变化一下 +- [洛谷 P1939【模板】矩阵加速(数列)](https://www.luogu.org/problemnew/show/P1939), $\text{base}$ 矩阵变成了 $3 \times 3$ 的矩阵,推导过程与上面差不多。 diff --git a/docs/math/misc.md b/docs/math/misc.md index d3931a97..c929b3b2 100644 --- a/docs/math/misc.md +++ b/docs/math/misc.md @@ -8,9 +8,7 @@ (为人教版高中数学 A 版教科书必修四内容) -> 平面的向量交错生长
-> 织成
-> 忧伤的网 +> 平面的向量交错生长
织成
忧伤的网 > > ——《膜你抄》 @@ -20,21 +18,21 @@ **有向线段**:带有方向的线段称为有向线段。有向线段有三要素:**起点,方向,长度**,知道了三要素,终点就唯一确定。我们用有向线段表示向量。 -**向量的模**:有向线段 $\vec{AB}$ 的长度称为向量的模,即为这个向量的大小。记为:$|\vec{AB}|$。 +**向量的模**:有向线段 $\vec{AB}$ 的长度称为向量的模,即为这个向量的大小。记为: $|\vec{AB}|$ 。 -**零向量**:模为 $0$ 的向量。零向量的方向任意。记为: $\vec{0}$ 或 $\mathbf{0}$。 +**零向量**:模为 $0$ 的向量。零向量的方向任意。记为: $\vec{0}$ 或 $\mathbf{0}$ 。 **单位向量**:模为 $1$ 的向量称为该方向上的单位向量。 -**平行向量**:方向相同或相反的两个**非零**向量。记作:$\vec a\parallel \vec b$。 +**平行向量**:方向相同或相反的两个**非零**向量。记作: $\vec a\parallel \vec b$ 。 **相等向量**:模相等且方向相同的向量。 **相反向量**:模相等且方向相反的向量。 -**向量的夹角**:已知两个非零向量 $\vec a,\vec b$,作 $\vec{OA}=\vec a,\vec{OB}=\vec b$,那么 $\theta=\angle \text{AOB}$ 就是向量 $\vec a$ 与向量 $\vec b$ 的夹角。记作:$\langle \vec a,\vec b\rangle$。显然当 $\theta =0$ 时两向量同向,$\theta=\pi$ 时两向量反向,$\theta=\frac{\pi}{2}$ 时我们说两向量垂直,记作 $\vec a\perp \vec b$。并且,我们规定 $\theta \in [0,\pi]$。 +**向量的夹角**:已知两个非零向量 $\vec a,\vec b$ ,作 $\vec{OA}=\vec a,\vec{OB}=\vec b$ ,那么 $\theta=\angle \text{AOB}$ 就是向量 $\vec a$ 与向量 $\vec b$ 的夹角。记作: $\langle \vec a,\vec b\rangle$ 。显然当 $\theta =0$ 时两向量同向, $\theta=\pi$ 时两向量反向, $\theta=\frac{\pi}{2}$ 时我们说两向量垂直,记作 $\vec a\perp \vec b$ 。并且,我们规定 $\theta \in [0,\pi]$ 。 -(呼…… 我们一口气把数学书上 2.1.3 之前的定义都给出了。) +(呼……我们一口气把数学书上 2.1.3 之前的定义都给出了。) 我们考虑平行向量,可以任作一条直线与这些向量平行,那么任一组平行向量都可以平移到同一直线上,所以平行向量又叫**共线向量**。 @@ -48,7 +46,7 @@ 我们定义了一种量,就希望让它具有运算。向量的运算可以类比数的运算,但是我们从物理学的角度出发研究向量的运算。 -我们考虑物理学中的位移概念,假如一个人从 $A$ 经 $B$ 走到 $C$,我们说他经过的位移为 $\vec{AB}+\vec{BC}$,这其实等价于这个人直接从 $A$ 走到 $C$,即 $\vec{AB}+\vec{BC}=\vec{AC}$。 +我们考虑物理学中的位移概念,假如一个人从 $A$ 经 $B$ 走到 $C$ ,我们说他经过的位移为 $\vec{AB}+\vec{BC}$ ,这其实等价于这个人直接从 $A$ 走到 $C$ ,即 $\vec{AB}+\vec{BC}=\vec{AC}$ 。 注意到力的合成法则——平行四边形法则,同样也可以看做一些向量相加。 @@ -61,24 +59,24 @@ 并且可以验证,向量的加法满足**交换律与结合律**。 -因为实数的减法可以写成加上相反数的形式,我们考虑在向量做减法时也这么写。即:$\vec a-\vec b=\vec a+(-\vec b)$。 +因为实数的减法可以写成加上相反数的形式,我们考虑在向量做减法时也这么写。即: $\vec a-\vec b=\vec a+(-\vec b)$ 。 这样,我们考虑共起点的向量,按照平行四边形法则做出它们的差,经过平移后可以发现**共起点向量的差向量是由减向量指向被减向量的有向线段**。 这也是向量减法的几何意义。 -我们有时候有两点 $A,B$,想知道 $\vec{AB}$,可以利用减法运算 $\vec{AB}=\vec{OB}-\vec{OA}$ 获得。 +我们有时候有两点 $A,B$ ,想知道 $\vec{AB}$ ,可以利用减法运算 $\vec{AB}=\vec{OB}-\vec{OA}$ 获得。 #### 向量的数乘 -考虑 $\vec b=\vec a+\vec a+\vec a$,$\vec b$ 等于 3 个 $\vec a$ 相加,我们记为 $\vec b=3\vec a$。 +考虑 $\vec b=\vec a+\vec a+\vec a$ , $\vec b$ 等于 3 个 $\vec a$ 相加,我们记为 $\vec b=3\vec a$ 。 -同样的,$\vec c=-\vec a-\vec a-\vec a$,那么 $\vec c=3(-\vec a)=-3\vec a$。 +同样的, $\vec c=-\vec a-\vec a-\vec a$ ,那么 $\vec c=3(-\vec a)=-3\vec a$ 。 -于是,一般地,我们规定实数 $\lambda$ 与向量 $\vec a$ 的积为一个向量,这种运算就是向量的**数乘运算**,记作 $\lambda \vec a$,它的长度与方向规定如下: +于是,一般地,我们规定实数 $\lambda$ 与向量 $\vec a$ 的积为一个向量,这种运算就是向量的**数乘运算**,记作 $\lambda \vec a$ ,它的长度与方向规定如下: -1. $|\lambda \vec a|=|\lambda||\vec a|$; -2. 当 $\lambda >0$ 时,$\lambda\vec a$ 与 $\vec a$ 同向,当 $\lambda =0$ 时,$\lambda \vec a=\vec 0$,当 $\lambda<0$ 时,$\lambda \vec a$ 与 $\vec a$ 方向相反。 +1. $|\lambda \vec a|=|\lambda||\vec a|$ ; +2. 当 $\lambda >0$ 时, $\lambda\vec a$ 与 $\vec a$ 同向,当 $\lambda =0$ 时, $\lambda \vec a=\vec 0$ ,当 $\lambda<0$ 时, $\lambda \vec a$ 与 $\vec a$ 方向相反。 我们根据数乘的定义,可以验证有如下运算律: @@ -95,11 +93,11 @@ $$ \lambda(\vec a-\vec b)=\lambda \vec a-\lambda \vec b $$ -由数乘的定义可知,对于**非零**向量 $\vec a$,如果存在实数 $\lambda$,使得 $\vec b=\lambda \vec a$,那么 $\vec a \parallel \vec b$。 +由数乘的定义可知,对于**非零**向量 $\vec a$ ,如果存在实数 $\lambda$ ,使得 $\vec b=\lambda \vec a$ ,那么 $\vec a \parallel \vec b$ 。 -反过来,如果 $\vec a\parallel \vec b$,$\vec a \not = \vec 0$,且 $|\vec b|=\mu |\vec a|$,那么当 $\vec a$ 与 $\vec b$ 同向时,$\vec b=\mu \vec a$,反向时 $\vec b=-\mu \vec a$。 +反过来,如果 $\vec a\parallel \vec b$ , $\vec a \not = \vec 0$ ,且 $|\vec b|=\mu |\vec a|$ ,那么当 $\vec a$ 与 $\vec b$ 同向时, $\vec b=\mu \vec a$ ,反向时 $\vec b=-\mu \vec a$ 。 -综上,我们有如下定理:**非零**向量 $\vec a$ 与 $\vec b$ 共线,当且仅当有唯一实数 $\lambda$,使得 $\vec b=\lambda \vec a$。 +综上,我们有如下定理:**非零**向量 $\vec a$ 与 $\vec b$ 共线,当且仅当有唯一实数 $\lambda$ ,使得 $\vec b=\lambda \vec a$ 。 最后,向量的加,减,数乘统称为向量的线性运算。 @@ -113,7 +111,7 @@ $$ 我们再加入一个向量,用两个**不共线**向量表示(两个共线向量在此可以看成同一个向量),这样我们可以把任意一个平面向量分解到这两个向量的方向上了。 -也就是说,如果两个向量 $\vec{e_1},\vec{e_2}$ 不共线,那么存在唯一实数对 $(x,y)$,使得与 $\vec{e_1},\vec{e_2}$ 共面的任意向量 $\vec p$ 满足 $\vec p=x\vec{e_1}+y\vec{e_2}$,这就是**平面向量基本定理**。在同一平面内的两个不共线的向量称为**基底**。 +也就是说,如果两个向量 $\vec{e_1},\vec{e_2}$ 不共线,那么存在唯一实数对 $(x,y)$ ,使得与 $\vec{e_1},\vec{e_2}$ 共面的任意向量 $\vec p$ 满足 $\vec p=x\vec{e_1}+y\vec{e_2}$ ,这就是**平面向量基本定理**。在同一平面内的两个不共线的向量称为**基底**。 如果基底相互垂直,那么我们在分解的时候就是对向量**正交分解**。 @@ -121,11 +119,11 @@ $$ 我们想把平面上的图形都放在平面直角坐标系下研究(这样形的问题就有了数作为依据)。 -我们可以取与 $x$ 轴与 $y$ 轴方向相同的单位向量 $i,j$ 作为一组基底,可以发现,$\vec p=x\vec i+y\vec j$,其中 $\vec p$ 为平面内任意向量。 +我们可以取与 $x$ 轴与 $y$ 轴方向相同的单位向量 $i,j$ 作为一组基底,可以发现, $\vec p=x\vec i+y\vec j$ ,其中 $\vec p$ 为平面内任意向量。 -由于平面向量基本定理,我们发现这样的实数对 $(x,y)$ 是唯一的。这样,平面内的任意向量都可以用有序实数对唯一确定,我们把 $(x,y)$ 叫做向量的**坐标**。记作:$\vec p=(x,y)$。 +由于平面向量基本定理,我们发现这样的实数对 $(x,y)$ 是唯一的。这样,平面内的任意向量都可以用有序实数对唯一确定,我们把 $(x,y)$ 叫做向量的**坐标**。记作: $\vec p=(x,y)$ 。 -这样没问题?是的,因为有序实数对 $(x,y)$ 与平面直角坐标系上的点唯一确定,那么我们作 $\vec{OP}=\vec p$,那么终点 $P(x,y)$ 也是唯一确定的。由于我们研究的都是自由向量,可以自由平移起点,这样,在平面直角坐标系里,每一个向量都可以用有序实数对唯一表示。 +这样没问题?是的,因为有序实数对 $(x,y)$ 与平面直角坐标系上的点唯一确定,那么我们作 $\vec{OP}=\vec p$ ,那么终点 $P(x,y)$ 也是唯一确定的。由于我们研究的都是自由向量,可以自由平移起点,这样,在平面直角坐标系里,每一个向量都可以用有序实数对唯一表示。 #### 平面向量的坐标运算 @@ -133,7 +131,7 @@ $$ 推导过程省略,下面给出结论: -若两向量 $\vec a=(m,n),\vec b=(p,q)$,则: +若两向量 $\vec a=(m,n),\vec b=(p,q)$ ,则: $$ \vec a+\vec b=(m+n,p+q)\\ @@ -141,7 +139,7 @@ $$ k\vec a=(km,kn) $$ -有了坐标运算,我们已知两点 $A(a,b),B(c,d)$,想知道 $\vec{AB}$ 就不难了,只需要 $\vec{OB}-\vec{OA}$ 即可,也就是 $\vec{AB}=(c-a,d-b)$。 +有了坐标运算,我们已知两点 $A(a,b),B(c,d)$ ,想知道 $\vec{AB}$ 就不难了,只需要 $\vec{OB}-\vec{OA}$ 即可,也就是 $\vec{AB}=(c-a,d-b)$ 。 有时候,我们需要将一个点 $P$ 沿一定方向平移某单位长度,这样我们把要平移的方向和距离组合成一个向量,利用向量加法的三角形法则,将 $\vec{OP}$ 加上这个向量,得到的向量终点即为平移后的点。 @@ -149,13 +147,13 @@ $$ 还可以判断向量共线,在此给出依据: -若 $A,B,C$ 三点共线,则 $\vec{OB}=\lambda \vec{OA}+(1-\lambda)\vec{OC}$。 +若 $A,B,C$ 三点共线,则 $\vec{OB}=\lambda \vec{OA}+(1-\lambda)\vec{OC}$ 。 证明很简单,~~留作课后作业。~~ ### 向量的数量积 -已知两个向量 $\vec a,\vec b$,它们的夹角为 $\theta$,那么: +已知两个向量 $\vec a,\vec b$ ,它们的夹角为 $\theta$ ,那么: $$ \vec a \cdot \vec b=|\vec a||\vec b|\cos \theta @@ -165,15 +163,15 @@ $$ 我们发现,这种运算得到的结果是一个实数,为标量,并不属于向量的线性运算。 -数量积有一些性质,在此留给读者进一步探究。在此提一点:$\vec a \perp \vec b$ 等价于 $\vec a\cdot \vec b=0$ +数量积有一些性质,在此留给读者进一步探究。在此提一点: $\vec a \perp \vec b$ 等价于 $\vec a\cdot \vec b=0$ 数量积具有几何意义:数量积 $\vec a \cdot \vec b$ 等于 $\vec a$ 的模与 $\vec b$ 在 $\vec a$ 方向上的投影的乘积。 按照坐标运算的推导过程,我们可以发现数量积的坐标运算: -若 $\vec a=(m,n),\vec b=(p,q)$,那么 $\vec a\cdot \vec b=mp+nq$。 +若 $\vec a=(m,n),\vec b=(p,q)$ ,那么 $\vec a\cdot \vec b=mp+nq$ 。 -还可以知道,向量的模 $|\vec a|=\sqrt {m^2+n^2}$。 +还可以知道,向量的模 $|\vec a|=\sqrt {m^2+n^2}$ 。 当然还可以知道向量夹角余弦值的表达式,式子太长不在这写了。 @@ -193,18 +191,18 @@ $$ #### 向量积 -我们定义向量 $\vec a,\vec b$ 的向量积为一个向量,记为 $\vec a\times \vec b$,其模与方向定义如下: +我们定义向量 $\vec a,\vec b$ 的向量积为一个向量,记为 $\vec a\times \vec b$ ,其模与方向定义如下: -1. $|\vec a\times \vec b|=|\vec a||\vec b|\sin \langle \vec a,\vec b\rangle$; -2. $\vec a\times \vec b$ 与 $\vec a,\vec b$ 都垂直,且 $\vec a,\vec b,\vec a\times \vec b$ 符合右手法则。 +1. $|\vec a\times \vec b|=|\vec a||\vec b|\sin \langle \vec a,\vec b\rangle$ ; +2. $\vec a\times \vec b$ 与 $\vec a,\vec b$ 都垂直,且 $\vec a,\vec b,\vec a\times \vec b$ 符合右手法则。 向量积也叫外积。 -由于向量积涉及到空间几何与线性代数知识,所以并未在高中课本中出现。然而注意到向量积的模,联想到三角形面积计算公式 $S=\frac{1}{2}ab\sin C$,我们可以发现向量积的几何意义是:**$|\vec a\times \vec b|$ 是以 $\vec a,\vec b$ 为邻边的平行四边形的面积**。 +由于向量积涉及到空间几何与线性代数知识,所以并未在高中课本中出现。然而注意到向量积的模,联想到三角形面积计算公式 $S=\frac{1}{2}ab\sin C$ ,我们可以发现向量积的几何意义是:** $|\vec a\times \vec b|$ 是以 $\vec a,\vec b$ 为邻边的平行四边形的面积**。 知道这个,多边形面积就很好算了。 -我们有一个不完全的坐标表示:记 $\vec a=(m,n),\vec b=(p,q)$,那么两个向量的向量积的竖坐标为 $mq-np$,我们根据右手法则和竖坐标符号可以推断出 $\vec b$ 相对于 $\vec a$ 的方向,若在逆时针方向竖坐标为正值,反之为负值,简记为**顺负逆正**。 +我们有一个不完全的坐标表示:记 $\vec a=(m,n),\vec b=(p,q)$ ,那么两个向量的向量积的竖坐标为 $mq-np$ ,我们根据右手法则和竖坐标符号可以推断出 $\vec b$ 相对于 $\vec a$ 的方向,若在逆时针方向竖坐标为正值,反之为负值,简记为**顺负逆正**。 ## 极坐标与极坐标系 @@ -220,13 +218,13 @@ $$ 然后我们介绍**弧度制**,把长度等于半径长的弧所对的圆心角称为 $1$ 弧度的角,用符号 $\text{rad}$ 表示,读作:弧度。 -一般地,正角的弧度数为正,负角的弧度数为负,零角的弧度数为 $0$,如果半径为 $r$ 的圆的圆心角 $\alpha$ 所对弧长为 $l$,则 $|\alpha|=\frac{l}{r}$。利用这个公式还可以写出弧长和扇形面积公式,在此略过。 +一般地,正角的弧度数为正,负角的弧度数为负,零角的弧度数为 $0$ ,如果半径为 $r$ 的圆的圆心角 $\alpha$ 所对弧长为 $l$ ,则 $|\alpha|=\frac{l}{r}$ 。利用这个公式还可以写出弧长和扇形面积公式,在此略过。 -那么,我们发现 $360^\circ$ 的角弧度数为 $2\pi$,这样有了对应关系之后,我们可以进行角度值和弧度制的转化了。 +那么,我们发现 $360^\circ$ 的角弧度数为 $2\pi$ ,这样有了对应关系之后,我们可以进行角度值和弧度制的转化了。 我们考虑一个角,将其终边再旋转一周,甚至多周,始边位置不动,那么终边位置永远是相同的,我们称这些角为**终边位置相同的角**。 -与角 $\alpha$ 终边位置相同的角的集合很容易得出,为 $\{\theta\mid \theta=\alpha+2k\pi,k\in \mathbb{Z}\}$。 +与角 $\alpha$ 终边位置相同的角的集合很容易得出,为 $\{\theta\mid \theta=\alpha+2k\pi,k\in \mathbb{Z}\}$ 。 可以理解为:给这个角不停加一圈,终边位置不变。 @@ -236,17 +234,17 @@ $$ > 某同学:学平面直角坐标系都学烦了,有没有其他坐标系? -我们考虑实际情况,比如航海,我们说「$B$ 在 $A$ 的北偏东 $30^\circ$ 方向上,距离为 $100$ 米」,而不是「以 $A$ 为原点建立平面直角坐标系,$B(50,50\sqrt 3)$」。 +我们考虑实际情况,比如航海,我们说「 $B$ 在 $A$ 的北偏东 $30^\circ$ 方向上,距离为 $100$ 米」,而不是「以 $A$ 为原点建立平面直角坐标系, $B(50,50\sqrt 3)$ 」。 -这样,我们在平面上选一定点 $O$,称为**极点**,自极点引出一条射线 $Ox$,称为**极轴**,再选择一个单位长度(在数学问题中通常为 $1$),一个角度单位(通常为弧度)及其正方向(通常为逆时针方向),这样就建立了**极坐标系**。 +这样,我们在平面上选一定点 $O$ ,称为**极点**,自极点引出一条射线 $Ox$ ,称为**极轴**,再选择一个单位长度(在数学问题中通常为 $1$ ),一个角度单位(通常为弧度)及其正方向(通常为逆时针方向),这样就建立了**极坐标系**。 在极坐标系下,我们怎么描述位置呢? -设 $A$ 为平面上一点,极点 $O$ 与 $A$ 之间的距离 $|OA|$ 即为**极径**,记为 $\rho$;以极轴为始边,$OA$ 为终边的角 $\angle xOA$ 为**极角**,记为 $\theta$,那么有序数对 $(\rho,\theta)$ 即为 $A$ 的**极坐标**。 +设 $A$ 为平面上一点,极点 $O$ 与 $A$ 之间的距离 $|OA|$ 即为**极径**,记为 $\rho$ ;以极轴为始边, $OA$ 为终边的角 $\angle xOA$ 为**极角**,记为 $\theta$ ,那么有序数对 $(\rho,\theta)$ 即为 $A$ 的**极坐标**。 -由终边相同的角的定义可知,$(\rho,\theta)$ 与 $(\rho,\theta+2k\pi)\ (k\in \mathbb{Z})$ 其实表示的是一样的点,特别地,极点的极坐标为 $(0,\theta)\ (\theta\in \mathbb{R})$,于是平面内的点的极坐标表示有无数多种。 +由终边相同的角的定义可知, $(\rho,\theta)$ 与 $(\rho,\theta+2k\pi)\ (k\in \mathbb{Z})$ 其实表示的是一样的点,特别地,极点的极坐标为 $(0,\theta)\ (\theta\in \mathbb{R})$ ,于是平面内的点的极坐标表示有无数多种。 -如果规定 $\rho>0,0\le \theta<2\pi​$,那么除极点外,其他平面内的点可以用唯一有序数对 $(\rho,\theta)​$ 表示,而极坐标 $(\rho,\theta)​$ 表示的点是唯一确定的。 +如果规定 $\rho>0,0\le \theta<2\pi​$ ,那么除极点外,其他平面内的点可以用唯一有序数对 $(\rho,\theta)​$ 表示,而极坐标 $(\rho,\theta)​$ 表示的点是唯一确定的。 当然,有时候研究极坐标系下的图形有些不方便,我们想要转到直角坐标系下研究,那么我们有互化公式。 @@ -266,6 +264,6 @@ $$ \tan \theta=\frac{y}{x}\ \ \ \ (x\not =0) $$ -于是,极角 $\theta=\arctan \frac{y}{x}$,这样就可以求出极角了。 +于是,极角 $\theta=\arctan \frac{y}{x}$ ,这样就可以求出极角了。 -在编程中,若要求反正切函数,尽量使用 `atan2(y, x)`,这个函数用途比 `atan(x)` 广泛。 +在编程中,若要求反正切函数,尽量使用 `atan2(y, x)` ,这个函数用途比 `atan(x)` 广泛。 diff --git a/docs/math/mobius.md b/docs/math/mobius.md index 5965f031..949b032f 100644 --- a/docs/math/mobius.md +++ b/docs/math/mobius.md @@ -1,6 +1,6 @@ ## 简介 -莫比乌斯反演是数论中的重要内容。对于一些函数 $f(n)$,如果很难直接求出它的值,而容易求出其倍数和或约数和 $g(n)$,那么可以通过莫比乌斯反演简化运算,求得 $f(n)$ 的值。 +莫比乌斯反演是数论中的重要内容。对于一些函数 $f(n)$ ,如果很难直接求出它的值,而容易求出其倍数和或约数和 $g(n)$ ,那么可以通过莫比乌斯反演简化运算,求得 $f(n)$ 的值。 开始学习莫比乌斯反演前,我们需要一些前置知识:**积性函数**、**Dirichlet 卷积**、**莫比乌斯函数**。 @@ -10,7 +10,7 @@ ### 定义 -若 $\gcd(x,y)=1$ 且 $f(xy)=f(x)f(y)$,则 $f(n)$ 为积性函数。 +若 $\gcd(x,y)=1$ 且 $f(xy)=f(x)f(y)$ ,则 $f(n)$ 为积性函数。 ### 性质 @@ -56,7 +56,7 @@ $$ ### 性质 -$\text{Dirichlet}$ 卷积满足交换律和结合律。 + $\text{Dirichlet}$ 卷积满足交换律和结合律。 其中 $\varepsilon$ 为 $\text{Dirichlet}$ 卷积的单位元(任何函数卷 $\varepsilon$ 都为其本身) @@ -77,7 +77,7 @@ $$ ### 定义 -$\mu$ 为莫比乌斯函数 + $\mu$ 为莫比乌斯函数 ### 性质 @@ -102,21 +102,21 @@ $$ \end{cases} $$ -其中 $\displaystyle\varepsilon(n)=\sum_{d\mid n}\mu(d)$ 即 $\varepsilon=\mu*1$ +其中 $\displaystyle\varepsilon(n)=\sum_{d\mid n}\mu(d)$ 即 $\varepsilon=\mu*1$ -设 $\displaystyle n=\prod_{i=1}^k{p_i}^{c_i},n'=\prod_{i=1}^k p_i$ +设 $\displaystyle n=\prod_{i=1}^k{p_i}^{c_i},n'=\prod_{i=1}^k p_i$ -那么 $\displaystyle\sum_{d\mid n}\mu(d)=\sum_{d\mid n'}\mu(d)=\sum_{i=0}^k C_k^i\cdot(-1)^k$ +那么 $\displaystyle\sum_{d\mid n}\mu(d)=\sum_{d\mid n'}\mu(d)=\sum_{i=0}^k C_k^i\cdot(-1)^k$ -根据二项式定理,易知该式子的值在 $k=0$ 即 $n=1$ 时值为 $1$ 否则为 $0$,这也同时证明了 $\displaystyle\sum_{d\mid n}\mu(d)=[n=1]$ +根据二项式定理,易知该式子的值在 $k=0$ 即 $n=1$ 时值为 $1$ 否则为 $0$ ,这也同时证明了 $\displaystyle\sum_{d\mid n}\mu(d)=[n=1]$ ### 补充结论 -反演结论:$\displaystyle [gcd(i,j)=1] \Leftrightarrow\sum_{d\mid\gcd(i,j)}\mu(d)$ +反演结论: $\displaystyle [gcd(i,j)=1] \Leftrightarrow\sum_{d\mid\gcd(i,j)}\mu(d)$ -- **直接推导**:如果看懂了上一个结论,这个结论稍加思考便可以推出:如果 $\gcd(i,j)=1$ 的话,那么代表着我们按上个结论中枚举的那个 $n$ 是 $1$,也就是式子的值是 $1$,反之,有一个与 $[\gcd(i,j)=1]$ 相同的值:$0$ +- **直接推导**:如果看懂了上一个结论,这个结论稍加思考便可以推出:如果 $\gcd(i,j)=1$ 的话,那么代表着我们按上个结论中枚举的那个 $n$ 是 $1$ ,也就是式子的值是 $1$ ,反之,有一个与 $[\gcd(i,j)=1]$ 相同的值: $0$ -- **利用 $\varepsilon$ 函数**:根据上一结论,$[\gcd(i,j)=1]\Rightarrow \varepsilon(\gcd(i,j))$,将 $\varepsilon$ 展开即可。 +- **利用 $\varepsilon$ 函数**:根据上一结论, $[\gcd(i,j)=1]\Rightarrow \varepsilon(\gcd(i,j))$ ,将 $\varepsilon$ 展开即可。 ### 线性筛 @@ -149,11 +149,11 @@ $$ \varphi*1=\text{ID}\text{(ID 函数即 } f(x)=x\text{)} $$ -将 $n$ 分解质因数:$\displaystyle n=\prod_{i=1}^k {p_i}^{c_i}$ +将 $n$ 分解质因数: $\displaystyle n=\prod_{i=1}^k {p_i}^{c_i}$ 首先,因为 $\varphi$ 是积性函数,故只要证明当 $n'=p^c$ 时 $\displaystyle\varphi*1=\sum_{d\mid n'}\varphi(\frac{n'}{d})=\text{ID}$ 成立即可。 -因为 $p$ 是质数,于是 $d=p^0,p^1,p^2,\cdots,p^c$ +因为 $p$ 是质数,于是 $d=p^0,p^1,p^2,\cdots,p^c$ 易知如下过程: @@ -167,7 +167,7 @@ $$ \end{aligned} $$ -该式子两侧同时卷 $\mu$ 可得 $\displaystyle\varphi(n)=\sum_{d\mid n}d\cdot\mu(\frac{n}{d})$ +该式子两侧同时卷 $\mu$ 可得 $\displaystyle\varphi(n)=\sum_{d\mid n}d\cdot\mu(\frac{n}{d})$ * * * @@ -197,13 +197,13 @@ $$ \sum_{d\mid n}\mu(d)f(\frac{n}{d})=\sum_{d\mid n}\mu(d)\sum_{k\mid \frac{n}{d}}g(k)=\sum_{k\mid n}g(k)\sum_{d\mid \frac{n}{k}}\mu(d)=g(n) $$ -用 $\displaystyle\sum_{d\mid n}g(d)$ 来替换 $f(\dfrac{n}{d})$,再变换求和顺序。最后一步转为的依据:$\displaystyle\sum_{d\mid n}\mu(d)=[n=1]$,因此在 $\dfrac{n}{k}=1$ 时第二个和式的值才为 $1$。此时 $n=k$,故原式等价于 $\displaystyle\sum_{k\mid n}[n=k]\cdot g(k)=g(n)$ +用 $\displaystyle\sum_{d\mid n}g(d)$ 来替换 $f(\dfrac{n}{d})$ ,再变换求和顺序。最后一步转为的依据: $\displaystyle\sum_{d\mid n}\mu(d)=[n=1]$ ,因此在 $\dfrac{n}{k}=1$ 时第二个和式的值才为 $1$ 。此时 $n=k$ ,故原式等价于 $\displaystyle\sum_{k\mid n}[n=k]\cdot g(k)=g(n)$ - **运用卷积**: -原问题为:已知 $f=g*1$,证明 $g=f*\mu$ +原问题为:已知 $f=g*1$ ,证明 $g=f*\mu$ -易知如下转化:$f*\mu=g*1*\mu\Rightarrow f*\mu=g$(其中 $1*\mu=\varepsilon$) +易知如下转化: $f*\mu=g*1*\mu\Rightarrow f*\mu=g$ (其中 $1*\mu=\varepsilon$ ) * * * @@ -229,7 +229,7 @@ $$ \sum_{i=1}^{\lfloor\frac{n}{k}\rfloor}\sum_{j=1}^{\lfloor\frac{m}{k}\rfloor}[\gcd(i,j)=1] $$ -因为 $\gcd(i,j)=1$ 时对答案才用贡献,于是我们可以将其替换为 $\varepsilon(\gcd(i,j))$($\varepsilon(n)$ 当且仅当 $n=1$ 时值为 $1$ 否则为 $0$ ),故原式化为 +因为 $\gcd(i,j)=1$ 时对答案才用贡献,于是我们可以将其替换为 $\varepsilon(\gcd(i,j))$ ( $\varepsilon(n)$ 当且仅当 $n=1$ 时值为 $1$ 否则为 $0$ ),故原式化为 $$ \sum_{i=1}^{\lfloor\frac{n}{k}\rfloor}\sum_{j=1}^{\lfloor\frac{m}{k}\rfloor}\varepsilon(\gcd(i,j)) @@ -254,9 +254,9 @@ $$ \displaystyle\sum_{d=1}^{\lfloor\frac{n}{k}\rfloor}\mu(d) \lfloor\frac{n}{kd}\rfloor\lfloor\frac{m}{kd}\rfloor $$ -很显然,式子可以数论分块求解(注意:过程中默认 $n\leqslant m$)。 +很显然,式子可以数论分块求解(注意:过程中默认 $n\leqslant m$ )。 -**时间复杂度**:$\Theta(N+T\sqrt{n})$ +**时间复杂度**: $\Theta(N+T\sqrt{n})$ **代码**: @@ -332,7 +332,7 @@ $$ \frac{1}{2}\cdot \sum_{i=1}^{n-1}\frac{n^2}{\gcd(i,n)}+n $$ -可以将相同的 $\gcd(i,n)$ 合并在一起计算,故只需要统计 $\gcd(i,n)=d$ 的个数。当 $\gcd(i,n)=d$ 时,$\displaystyle\gcd(\frac{i}{d},\frac{n}{d})=1$,所以 $\gcd(i,n)=d$ 的个数有 $\displaystyle\varphi(\frac{n}{d})$ 个。 +可以将相同的 $\gcd(i,n)$ 合并在一起计算,故只需要统计 $\gcd(i,n)=d$ 的个数。当 $\gcd(i,n)=d$ 时, $\displaystyle\gcd(\frac{i}{d},\frac{n}{d})=1$ ,所以 $\gcd(i,n)=d$ 的个数有 $\displaystyle\varphi(\frac{n}{d})$ 个。 故答案为 @@ -340,15 +340,15 @@ $$ \frac{1}{2}\cdot\sum_{d\mid n}\frac{n^2\cdot\varphi(\frac{n}{d})}{d}+n $$ -变换求和顺序,设 $\displaystyle d'=\frac{n}{d}$,式子化为 +变换求和顺序,设 $\displaystyle d'=\frac{n}{d}$ ,式子化为 $$ \frac{1}{2}n\cdot\sum_{d'\mid n}d'\cdot\varphi(d')+n $$ -设 $\displaystyle \text{g}(n)=\sum_{d\mid n} d\cdot\varphi(d)$,已知 $\text{g}$ 为积性函数,于是可以 $\Theta(n)$ 预处理。最后枚举 $d$,统计贡献即可。 +设 $\displaystyle \text{g}(n)=\sum_{d\mid n} d\cdot\varphi(d)$ ,已知 $\text{g}$ 为积性函数,于是可以 $\Theta(n)$ 预处理。最后枚举 $d$ ,统计贡献即可。 -**时间复杂度**:$\Theta(n\log n)$ +**时间复杂度**: $\Theta(n\log n)$ **代码**: @@ -404,7 +404,7 @@ $$ \sum_{i=1}^n\sum_{j=1}^m\frac{i\cdot j}{\gcd(i,j)} $$ -枚举最大公因数 $d$,显然两个数除以 $d$ 得到的数互质 +枚举最大公因数 $d$ ,显然两个数除以 $d$ 得到的数互质 $$ \sum_{i=1}^n\sum_{j=1}^m\sum_{d\mid i,d\mid j,\gcd(\frac{i}{d},\frac{j}{d})=1}\frac{i\cdot j}{d} @@ -422,13 +422,13 @@ $$ \text{sum}(n,m)=\sum_{i=1}^n\sum_{j=1}^m [\gcd(i,j)=1]\ i\cdot j $$ -接下来对 $\text{sum}(n,m)$ 进行化简。首先枚举约数,并将 $[\gcd(i,j)=1]$ 表示为 $\varepsilon(\gcd(i,j))$ +接下来对 $\text{sum}(n,m)$ 进行化简。首先枚举约数,并将 $[\gcd(i,j)=1]$ 表示为 $\varepsilon(\gcd(i,j))$ $$ \sum_{d=1}^n\sum_{d\mid i}^n\sum_{d\mid j}^m\mu(d)\cdot i\cdot j $$ -设 $i=i'\cdot d$,$j=j'\cdot d$,显然式子可以变为 +设 $i=i'\cdot d$ , $j=j'\cdot d$ ,显然式子可以变为 $$ \sum_{d=1}^n\mu(d)\cdot d^2\cdot\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum_{j=1}^{\lfloor\frac{m}{d}\rfloor}i\cdot j @@ -458,9 +458,9 @@ $$ 可见这又是一个可以数论分块求解的式子! -本题除了推式子比较复杂、代码细节较多之外,是一道很好的莫比乌斯反演练习题!(上述过程中,默认 $n\leqslant m$) +本题除了推式子比较复杂、代码细节较多之外,是一道很好的莫比乌斯反演练习题!(上述过程中,默认 $n\leqslant m$ ) -**时间复杂度**:$\Theta(n+m)$(两次数论分块) +**时间复杂度**: $\Theta(n+m)$ (两次数论分块) **代码**: @@ -520,4 +520,4 @@ int main() { } ``` -> 本文部分内容引用于 [algocode 算法博客](https://algocode.net),特别鸣谢! +> 本文部分内容引用于[algocode 算法博客](https://algocode.net),特别鸣谢! diff --git a/docs/math/ntt.md b/docs/math/ntt.md index 87c6aeb1..fbe6353e 100644 --- a/docs/math/ntt.md +++ b/docs/math/ntt.md @@ -1,4 +1,4 @@ -(本文转载自 [桃酱的算法笔记](https://zhuanlan.zhihu.com/c_1005817911142838272),原文戳 [链接](https://zhuanlan.zhihu.com/p/41867199),已获得作者授权) +(本文转载自[桃酱的算法笔记](https://zhuanlan.zhihu.com/c_1005817911142838272),原文戳[链接](https://zhuanlan.zhihu.com/p/41867199),已获得作者授权) ## 简介 @@ -6,46 +6,46 @@ NTT 解决的是多项式乘法带模数的情况,可以说有些受模数的 但是它比较方便呀毕竟没有复数部分 qwq -## 学习 NTT 之前... +## 学习 NTT 之前…… ### 生成子群 -子群:群 $(S,⊕), (S′,⊕)$, 满足 $S′⊂S$,则 $(S′,⊕)$ 是 $(S,⊕)$ 的子群 +子群:群 $(S,⊕), (S′,⊕)$ , 满足 $S′⊂S$ ,则 $(S′,⊕)$ 是 $(S,⊕)$ 的子群 -拉格朗日定理:$|S′|∣|S |$ 证明需要用到陪集,得到陪集大小等于子群大小,每个陪集要么不想交要么相等,所有陪集的并是集合 $S$,那么显然成立。 +拉格朗日定理: $|S′|∣|S |$ 证明需要用到陪集,得到陪集大小等于子群大小,每个陪集要么不想交要么相等,所有陪集的并是集合 $S$ ,那么显然成立。 -生成子群:$a \in S$ ​的生成子群 $\left = \{a^{(k)}, k \geq 1 \}$ ​,$a$ 是 $\left< a \right>$ 的生成元 +生成子群: $a \in S$ ​的生成子群 $\left = \{a^{(k)}, k \geq 1 \}$ ​, $a$ 是 $\left< a \right>$ 的生成元 -阶:群 $S$ 中 $a$ 的阶是满足 $a^r=e$ 的最小的 $r$, 符号 $\operatorname{ord}(a)$ , 有 $\operatorname{ord}(a)=\left|\left\right|$,显然成立。 +阶:群 $S$ 中 $a$ 的阶是满足 $a^r=e$ 的最小的 $r$ , 符号 $\operatorname{ord}(a)$ , 有 $\operatorname{ord}(a)=\left|\left\right|$ ,显然成立。 -考虑群 $Z_n^ \times =\{[a], n \in Z_n : \gcd(a, n) = 1\}, |Z_n^ \times | = \phi(n)$ +考虑群 $Z_n^ \times =\{[a], n \in Z_n : \gcd(a, n) = 1\}, |Z_n^ \times | = \phi(n)$ -阶就是满足 $a^r \equiv 1 (\bmod n)$ ​的最小的 $r$, $\operatorname{ord}(a)=r$ +阶就是满足 $a^r \equiv 1 (\bmod n)$ ​的最小的 $r$ , $\operatorname{ord}(a)=r$ ### [原根](/math/primitive-root) -$g$ 满足 $\operatorname{ord}_n(g)=\left|Z_n^\times\right|=\phi(n)$,对于质数 $p$,也就是说 $g^i \bmod p, 0 \leq i < p$ 结果互不相同. + $g$ 满足 $\operatorname{ord}_n(g)=\left|Z_n^\times\right|=\phi(n)$ ,对于质数 $p$ ,也就是说 $g^i \bmod p, 0 \leq i < p$ 结果互不相同。 -模 $n$ 有原根的充要条件 : $n = 2, 4, p^e, 2 \times p^e$ +模 $n$ 有原根的充要条件 : $n = 2, 4, p^e, 2 \times p^e$ -离散对数:$g^t \equiv a (\bmod n),ind_{n,g}{(a)}=t$ +离散对数: $g^t \equiv a (\bmod n),ind_{n,g}{(a)}=t$ ​因为 $g$ 是原根,所以 $gt$ 每 $\phi(n)$ 是一个周期,可以取到 $| Z \times n |$ 的所有元素 对于 $n$ 是质数时,就是得到 $[1,n−1]$ 的所有数,就是 $[0,n−2]$ 到 $[1,n−1]$ 的映射 离散对数满足对数的相关性质,如 -求原根可以证明满足 $g^r \equiv 1(\bmod p)$ +求原根可以证明满足 $g^r \equiv 1(\bmod p)$ ​的最小的 $r$ 一定是 $p−1$ 的约数 -对于质数 $p$,质因子分解 $p−1$,若 $g^{(p-1)/pi} \neq 1 (\bmod p)$ +对于质数 $p$ ,质因子分解 $p−1$ ,若 $g^{(p-1)/pi} \neq 1 (\bmod p)$ -​恒成立,$g$ 为 $p$ 的原根 +​恒成立, $g$ 为 $p$ 的原根 ## NTT -对于质数 $p=qn+1, (n=2^m)$ ​, 原根 $g$ 满足 $g^{qn} \equiv 1 (\bmod p)$​, 将 $g_n=g^p(\bmod q)$ 看做 $\omega_n$ 的等价,择其满足相似的性质,比如 $g_n^n \equiv 1 (\bmod p), g_n^{n/2} \equiv -1 (\bmod p)$ +对于质数 $p=qn+1, (n=2^m)$ ​, 原根 $g$ 满足 $g^{qn} \equiv 1 (\bmod p)$ ​, 将 $g_n=g^p(\bmod q)$ 看做 $\omega_n$ 的等价,择其满足相似的性质,比如 $g_n^n \equiv 1 (\bmod p), g_n^{n/2} \equiv -1 (\bmod p)$ -然后因为这里涉及到数论变化,所以这里的 $N$(为了区分 FFT 中的 n,我们把这里的 n 称为 $N$)可以比 FFT 中的 n 大,但是只要把 $\frac{qN}{n}$ 看做这里的 $q$ 就行了,能够避免大小问题。。。 +然后因为这里涉及到数论变化,所以这里的 $N$ (为了区分 FFT 中的 n,我们把这里的 n 称为 $N$ )可以比 FFT 中的 n 大,但是只要把 $\frac{qN}{n}$ 看做这里的 $q$ 就行了,能够避免大小问题…… 常见的有 @@ -57,13 +57,13 @@ $$ p=998244353=2 \times 17 \times 2^{23}+1, g=3 $$ -就是 $g^{qn}$ 的等价 $e^{2\pi n}$ +就是 $g^{qn}$ 的等价 $e^{2\pi n}$ -迭代到长度 $l$ 时 $g_l = g^{\frac{p-1}{l}}$, 或者 $\omega_n = g_l = g_N^{\frac{N}{l}} = g_N^{\frac{p-1}{l}}$ +迭代到长度 $l$ 时 $g_l = g^{\frac{p-1}{l}}$ , 或者 $\omega_n = g_l = g_N^{\frac{N}{l}} = g_N^{\frac{p-1}{l}}$ 接下来放一个大数相乘的模板 -参考网址如下 +参考网址如下 ```c++ #include diff --git a/docs/math/prime.md b/docs/math/prime.md index 40f394d0..7fd9e77a 100644 --- a/docs/math/prime.md +++ b/docs/math/prime.md @@ -1,8 +1,8 @@ -我们说,如果存在一个整数 $k$,使得 $a = kd$,则称 $d$ 整除 $a$,记做 $d | a$,称 $a$ 是 $d$ 的倍数,如果 $d > 0$,称 $d$ 是 $a$ 的约数。特别地,任何整数都整除 $0$。 +我们说,如果存在一个整数 $k$ ,使得 $a = kd$ ,则称 $d$ 整除 $a$ ,记做 $d | a$ ,称 $a$ 是 $d$ 的倍数,如果 $d > 0$ ,称 $d$ 是 $a$ 的约数。特别地,任何整数都整除 $0$ 。 -显然大于 $1$ 的正整数 $a$ 可以被 $1$ 和 $a$ 整除,如果除此之外 $a$ 没有其他的约数,则称 $a$ 是素数,又称质数。任何一个大于 $1$ 的整数如果不是素数,也就是有其他约数,就称为是合数。$1$ 既不是合数也不是素数。 +显然大于 $1$ 的正整数 $a$ 可以被 $1$ 和 $a$ 整除,如果除此之外 $a$ 没有其他的约数,则称 $a$ 是素数,又称质数。任何一个大于 $1$ 的整数如果不是素数,也就是有其他约数,就称为是合数。 $1$ 既不是合数也不是素数。 -素数计数函数:小于或等于 $x$ 的素数的个数,用 $\pi(x)$ 表示。随着 $x$ 的增大,有这样的近似结果:$\pi(x) \sim \frac{x}{\ln(x)}$ +素数计数函数:小于或等于 $x$ 的素数的个数,用 $\pi(x)$ 表示。随着 $x$ 的增大,有这样的近似结果: $\pi(x) \sim \frac{x}{\ln(x)}$ ## 素数判定 @@ -24,7 +24,7 @@ bool isPrime(a) { 很容易发现这样一个事实:如果 $x$ 是 $a$ 的约数,那么 $\frac{a}{x}$ 也是 $a$ 的约数。 -这个结论告诉我们,对于每一对 $(x, \frac{a}{x} )$,只需要检验其中的一个就好了。为了方便起见,我们之考察每一对里面小的那个数。不难发现,所有这些较小数就是 $[1, \sqrt{a}]$ 这个区间里的数。 +这个结论告诉我们,对于每一对 $(x, \frac{a}{x} )$ ,只需要检验其中的一个就好了。为了方便起见,我们之考察每一对里面小的那个数。不难发现,所有这些较小数就是 $[1, \sqrt{a}]$ 这个区间里的数。 由于 $1$ 肯定是约数,所以不检验它。 @@ -42,9 +42,9 @@ Miller-Rabin 素性测试(Miller–Rabin primality test)是进阶的素数 #### Fermat 素性测试 -我们可以根据 [费马小定理](/math/fermat/#_1) 得出一种检验素数的思路: +我们可以根据[费马小定理](/math/fermat/#_1)得出一种检验素数的思路: -它的基本思想是不断地选取在 $[2, n-1]$ 中的基 $a$,并检验是否每次都有 $a^{n-1} \equiv 1 \pmod n$ +它的基本思想是不断地选取在 $[2, n-1]$ 中的基 $a$ ,并检验是否每次都有 $a^{n-1} \equiv 1 \pmod n$ ```c++ bool millerRabin(int n) { @@ -56,29 +56,29 @@ bool millerRabin(int n) { } ``` -很遗憾,费马小定理的逆定理并不成立,换言之,满足了 $a^{n-1} \equiv 1 \pmod n$ ,$n$ 也不一定是素数。 +很遗憾,费马小定理的逆定理并不成立,换言之,满足了 $a^{n-1} \equiv 1 \pmod n$ , $n$ 也不一定是素数。 #### 卡迈克尔数 -上面的做法中随机地选择 $a$,很大程度地降低了犯错的概率。但是仍有一类数,上面的做法并不能准确地判断。 +上面的做法中随机地选择 $a$ ,很大程度地降低了犯错的概率。但是仍有一类数,上面的做法并不能准确地判断。 -对于合数 $n$,如果对于所有正整数 $a$,$a$ 和 $n$ 互素,都有同余式 $a^{n-1} \equiv 1 \pmod n$ 成立,则合数 $n$ 为卡迈克尔数(Carmichael Number),又称为费马伪素数。 +对于合数 $n$ ,如果对于所有正整数 $a$ , $a$ 和 $n$ 互素,都有同余式 $a^{n-1} \equiv 1 \pmod n$ 成立,则合数 $n$ 为卡迈克尔数(Carmichael Number),又称为费马伪素数。 -比如,$341 = 11 \times 31$ 就是一个卡迈克尔数。 +比如, $341 = 11 \times 31$ 就是一个卡迈克尔数。 而且我们知道,若 $n$ 为卡迈克尔数,则 $m=2^{n}-1$ 也是一个卡迈克尔数,从而卡迈克尔数的个数是无穷的。 #### 二次探测定理 -如果 $p$ 是奇素数,则 $x^2 \equiv 1 \bmod p$ 的解为 $x = 1$ 或者 $x = p - 1 (\bmod p)$; +如果 $p$ 是奇素数,则 $x^2 \equiv 1 \bmod p$ 的解为 $x = 1$ 或者 $x = p - 1 (\bmod p)$ ; ### 实现 -根据卡迈克尔数的性质,可知其一定不是 $p^e$。 +根据卡迈克尔数的性质,可知其一定不是 $p^e$ 。 不妨将费马小定理和二次探测定理结合起来使用: -将 $n−1$ 分解为 $n−1=u \times 2^t$,不断地对 $u$ 进行平方操作,若发现非平凡平方根时即可判断出其不是素数。 +将 $n−1$ 分解为 $n−1=u \times 2^t$ ,不断地对 $u$ 进行平方操作,若发现非平凡平方根时即可判断出其不是素数。 比较正确的 Miller Rabin:(来自 fjzzq2002) @@ -112,11 +112,11 @@ bool millerRabbin(int n) { 如果某个正整数 $n$ 满足如下条件,则称为是反素数: 任何小于 $n$ 的正数的约数个数都小于 $n$ 的约数个数 -注:注意区分 [emirp](https://en.wikipedia.org/wiki/Emirp),它是用来表示从后向前写读是素数的数。 +注:注意区分[emirp](https://en.wikipedia.org/wiki/Emirp),它是用来表示从后向前写读是素数的数。 ### 简介 -(本段转载自 [桃酱的算法笔记](https://zhuanlan.zhihu.com/c_1005817911142838272),原文戳 [链接](https://zhuanlan.zhihu.com/p/41759808),已获得作者授权) +(本段转载自[桃酱的算法笔记](https://zhuanlan.zhihu.com/c_1005817911142838272),原文戳[链接](https://zhuanlan.zhihu.com/p/41759808),已获得作者授权) 其实顾名思义,素数就是因子只有两个的数,那么反素数,就是因子最多的数(并且因子个数相同的时候值最小),所以反素数是相对于一个集合来说的。 @@ -124,7 +124,7 @@ bool millerRabbin(int n) { 那么,如何来求解反素数呢? -首先,既然要求因子数,我首先想到的就是素因子分解。把 $n$ 分解成 $n=p_{1}^{k_{1}}p_{2}^{k_{2}} \cdots p_{n}^{k_{n}}$ 的形式,其中 $p$ 是素数,$k$ 为他的指数。这样的话总因子个数就是 $(k_1+1) \times (k_2+1) \times (k_3+1) \cdots \times (k_n+1)$。 +首先,既然要求因子数,我首先想到的就是素因子分解。把 $n$ 分解成 $n=p_{1}^{k_{1}}p_{2}^{k_{2}} \cdots p_{n}^{k_{n}}$ 的形式,其中 $p$ 是素数, $k$ 为他的指数。这样的话总因子个数就是 $(k_1+1) \times (k_2+1) \times (k_3+1) \cdots \times (k_n+1)$ 。 但是显然质因子分解的复杂度是很高的,并且前一个数的结果不能被后面利用。所以要换个方法。 @@ -132,7 +132,7 @@ bool millerRabbin(int n) { 1. 反素数肯定是从 $2$ 开始的连续素数的幂次形式的乘积。 -2. 数值小的素数的幂次大于等于数值大的素数,即 $n=p_{1}^{k_{1}}p_{2}^{k_{2}} \cdots p_{n}^{k_{n}}$ 中,有 $k_1 \geq k_2 \geq k_3 \geq \cdots \geq k_n$ +2. 数值小的素数的幂次大于等于数值大的素数,即 $n=p_{1}^{k_{1}}p_{2}^{k_{2}} \cdots p_{n}^{k_{n}}$ 中,有 $k_1 \geq k_2 \geq k_3 \geq \cdots \geq k_n$ 解释: @@ -142,13 +142,13 @@ bool millerRabbin(int n) { 另外还有两个问题, -1. 对于给定的 $n$,要枚举到哪一个素数呢? +1. 对于给定的 $n$ ,要枚举到哪一个素数呢? 最极端的情况大不了就是 $n=p_{1}*p_{2} \cdots p_{n}$ ,所以只要连续素数连乘到刚好小于等于 $n$ 就可以的呢。再大了,连全都一次幂,都用不了,当然就是用不到的啦! 2. 我们要枚举到多少次幂呢? -我们考虑一个极端情况,当我们最小的素数的某个幂次已经比所给的 $n$(的最大值)大的话,那么展开成其他的形式,最大幂次一定小于这个幂次。unsigned long long 的最大值是 2 的 64 次方,所以我这边习惯展开成 2 的 64 次方。 +我们考虑一个极端情况,当我们最小的素数的某个幂次已经比所给的 $n$ (的最大值)大的话,那么展开成其他的形式,最大幂次一定小于这个幂次。unsigned long long 的最大值是 2 的 64 次方,所以我这边习惯展开成 2 的 64 次方。 细节有了,那么我们具体如何具体实现呢? @@ -160,7 +160,7 @@ bool millerRabbin(int n) { 3. 当前因子大于我们想要的因子了 -4. 当前因子正好是我们想要的因子(此时判断是否需要更新最小 $ans$) +4. 当前因子正好是我们想要的因子(此时判断是否需要更新最小 $ans$ ) 然后 dfs 里面不断一层一层枚举次数继续往下迭代就好啦\~~ diff --git a/docs/math/quick-pow.md b/docs/math/quick-pow.md index 7a208abb..5179e569 100644 --- a/docs/math/quick-pow.md +++ b/docs/math/quick-pow.md @@ -1,8 +1,8 @@ 快速幂,是一种求 $a^b \bmod p$ 的方法,得益于将指数按二进制拆开的思想。 -事实上,根据模运算的性质,$a \times b \bmod p = ((a \bmod p) \times b) \bmod p$。那么我们也可以把 $a^b \mod p$ 分解成一系列比较小的数的乘积。 +事实上,根据模运算的性质, $a \times b \bmod p = ((a \bmod p) \times b) \bmod p$ 。那么我们也可以把 $a^b \mod p$ 分解成一系列比较小的数的乘积。 -如果把 $b$ 写作二进制为 $a_ta_{t-1} \cdots a_1a_0$,那么有: +如果把 $b$ 写作二进制为 $a_ta_{t-1} \cdots a_1a_0$ ,那么有: $$ b = a_t2^2 + a_{t-1}2^{t-1} + a_{t-2}2^{t-2} + \cdots + a_12^1 + a_02^0 @@ -20,33 +20,33 @@ $$ 根据上式我们发现,原问题被我们转化成了形式相同的子问题的乘积。 -最重要的是,我们注意到,$a^{2^{i+1}} \bmod c = (a^{2^i})^2 \bmod c$,可以再常数时间内从 $2^i$ 项推出 $2^{i+1}$ 项。于是,原问题总的复杂度就是 $O(logb)$ +最重要的是,我们注意到, $a^{2^{i+1}} \bmod c = (a^{2^i})^2 \bmod c$ ,可以再常数时间内从 $2^i$ 项推出 $2^{i+1}$ 项。于是,原问题总的复杂度就是 $O(logb)$ 在算法竞赛中,快速幂的思想不仅用于整数乘法,也可用于大整数加法,矩阵幂运算等场合中。 如果你看不懂,那就简单点说吧。 -举个栗子,$a^{10}$ 等价于下面的式子: +举个栗子, $a^{10}$ 等价于下面的式子: -$a \times a \times a \times a \times a \times a \times a \times a \times a \times a$ + $a \times a \times a \times a \times a \times a \times a \times a \times a \times a$ -通过观察我们不难发现,$a^{10}$ 可以转化成 $(a \times a)^{5}$ +通过观察我们不难发现, $a^{10}$ 可以转化成 $(a \times a)^{5}$ -$\left(a \times a \right) \times\left(a \times a \right) \times \left(a \times a \right) \times \left(a \times a \right) \times \left(a \times a \right)$ + $\left(a \times a \right) \times\left(a \times a \right) \times \left(a \times a \right) \times \left(a \times a \right) \times \left(a \times a \right)$ -这时,再进行分解,我们假设$a' =a \times a$,原式就成了 +这时,再进行分解,我们假设 $a' =a \times a$ ,原式就成了 $$ a'\times a'\times a'\times a'\times a' $$ -可是我们发现,a 不能正好分完,于是我们单独拎出来一个 a',就转化成了 : +可是我们发现,a 不能正好分完,于是我们单独拎出来一个 a',就转化成了: -$\left (a' \times a'\right) \times\left (a' \times a'\right) \times a'$ + $\left (a' \times a'\right) \times\left (a' \times a'\right) \times a'$ 如此重复下去即可,终止条件: -$a^0=1$ 和 $a^1=a$ + $a^0=1$ 和 $a^1=a$ ## 实现代码 diff --git a/docs/math/sieve.md b/docs/math/sieve.md index 9fb3cc01..d7438f68 100644 --- a/docs/math/sieve.md +++ b/docs/math/sieve.md @@ -24,7 +24,7 @@ int Eratosthenes(int n) { } ``` -以上为 **Eratosthenes 筛法**(埃拉托斯特尼筛法),时间复杂度是 $O(n\log\log n)$。 +以上为**Eratosthenes 筛法**(埃拉托斯特尼筛法),时间复杂度是 $O(n\log\log n)$ 。 以上做法仍有优化空间,我们发现这里面似乎会对某些数标记了很多次其为合数。有没有什么办法省掉无意义的步骤呢? @@ -61,18 +61,18 @@ void init() { 上面代码中的 $phi$ 数组,会在下面提到。 -上面的这种**线性筛法**也称为 **Euler 筛法**(欧拉筛法)。 +上面的这种**线性筛法**也称为**Euler 筛法**(欧拉筛法)。 ??? note 注意到筛法求素数的同时也得到了每个数的最小质因子 ## 筛法求欧拉函数 -注意到在线性筛中,每一个合数都是被最小的质因子筛掉。比如设 $p_1$ 是 $n$ 的最小质因子,$n' = \frac{n}{p_1}$,那么线性筛的过程中 $n$ 通过 $n' \times p_1$ 筛掉。 +注意到在线性筛中,每一个合数都是被最小的质因子筛掉。比如设 $p_1$ 是 $n$ 的最小质因子, $n' = \frac{n}{p_1}$ ,那么线性筛的过程中 $n$ 通过 $n' \times p_1$ 筛掉。 -观察线性筛的过程,我们还需要处理两个部分,下面对$n' \bmod p_1$ 分情况讨论。 +观察线性筛的过程,我们还需要处理两个部分,下面对 $n' \bmod p_1$ 分情况讨论。 -如果 $n' \bmod p_1 = 0$,那么 $n'$ 包含了 $n$ 的所有质因子。 +如果 $n' \bmod p_1 = 0$ ,那么 $n'$ 包含了 $n$ 的所有质因子。 $$ \begin{aligned} @@ -128,8 +128,7 @@ void pre() { ## 筛法求约数和 -$f_i$表示$i$的约数和 -$g_i$表示$i$的最小质因子的$p+p^1+p^2+\dots p^k$ + $f_i$ 表示 $i$ 的约数和 $g_i$ 表示 $i$ 的最小质因子的 $p+p^1+p^2+\dots p^k$ ```cpp void pre() { diff --git a/docs/math/stirling.md b/docs/math/stirling.md index fa67e653..343982a9 100644 --- a/docs/math/stirling.md +++ b/docs/math/stirling.md @@ -1,9 +1,9 @@ ## Stirling 数(子集划分) 根据例题来讲解: -(2007 普及)将$n$个数$(1,2,…,n)$分成$r$个部分。每个部分至少一个数。将不同划分方法的总数记为$S_n^r$。例如,$S_4^2=7$,这 7 种不同的划分方法依次为 $\{\ (1) , (234) \}\,\{\ (2) , (134) \}\,\{\ (3) , (124) \}\,\{\ (4) , (123) \}\,\{\ (12) , (34) \}\,\{\ (13) , (24) \}\,\{\ (14) , (23) \}$。当$n=6,r=3$时,$S_6^3$=( ) +(2007 普及)将 $n$ 个数 $(1,2,…,n)$ 分成 $r$ 个部分。每个部分至少一个数。将不同划分方法的总数记为 $S_n^r$ 。例如, $S_4^2=7$ ,这 7 种不同的划分方法依次为 $\{\ (1) , (234) \}\,\{\ (2) , (134) \}\,\{\ (3) , (124) \}\,\{\ (4) , (123) \}\,\{\ (12) , (34) \}\,\{\ (13) , (24) \}\,\{\ (14) , (23) \}$ 。当 $n=6,r=3$ 时, $S_6^3$ =() -> 提示:先固定一个数,对于其余的 5 个数考虑$S_5^3$与$S_5^2$,再分这两种情况对原固定的数进行分析。 +> 提示:先固定一个数,对于其余的 5 个数考虑 $S_5^3$ 与 $S_5^2$ ,再分这两种情况对原固定的数进行分析。 题解:在近几年算法竞赛中,递推算法越来越重要: diff --git a/docs/misc/cdq-divide.md b/docs/misc/cdq-divide.md index 4cbf62be..25e4988e 100644 --- a/docs/misc/cdq-divide.md +++ b/docs/misc/cdq-divide.md @@ -8,29 +8,29 @@ **2.cdq 分治优化 1D/1D 动态规划的转移** -**3. 通过 cdq 分治, 将一些动态问题转化为静态问题** +**3. 通过 cdq 分治,将一些动态问题转化为静态问题** * * * ## CDQ 分治解决和点对有关的问题 -这类问题一般是给你一个长度为 n 的序列,然后让你统计有一些特性的点对$(i,j)$有多少个, 又或者说是找到一对点$(i,j)$使得一些函数的值最大之类的问题 +这类问题一般是给你一个长度为 n 的序列,然后让你统计有一些特性的点对 $(i,j)$ 有多少个,又或者说是找到一对点 $(i,j)$ 使得一些函数的值最大之类的问题 那么 cdq 分治基于这样一个算法流程解决这类问题 -**1. 找到这个序列的中点$mid$** +**1. 找到这个序列的中点 $mid$ ** -**2. 将所有点对$(i,j)$划分为 3 类** +**2. 将所有点对 $(i,j)$ 划分为 3 类** -**第一种是$1 \leq i \leq mid,1 \leq j \leq mid$的点对** +**第一种是 $1 \leq i \leq mid,1 \leq j \leq mid$ 的点对** -**第二种是$1 \leq i \leq mid ,mid+1 \leq j \leq n$的点对** +**第二种是 $1 \leq i \leq mid ,mid+1 \leq j \leq n$ 的点对** -**第三种是$mid+1 \leq i \leq n,mid+1 \leq j \leq n$的点对** +**第三种是 $mid+1 \leq i \leq n,mid+1 \leq j \leq n$ 的点对** -**3. 将$(1,n)$这个序列拆成两个序列$(1,mid)$和$(mid+1,n)$** +**3. 将 $(1,n)$ 这个序列拆成两个序列 $(1,mid)$ 和 $(mid+1,n)$ ** -**会发现第一类点对和第三类点对都在这两个序列之中, 递归的去解决这两类点对** +**会发现第一类点对和第三类点对都在这两个序列之中,递归的去解决这两类点对** **4. 想方设法处理一下第二类点对的信息** @@ -38,7 +38,7 @@ _实际应用的时候我们通常都是写一个函数 $solve(l,r)$ 表示我 _所以刚才的算法流程中的递归部分我们就是通过 $solve(l,mid),solve(mid,r)$ 来实现的_ -所以说 cdq 分治只是一种十分模糊的思想,可以看到这种思想就是不断的把点对通过递归~~(甩锅)~~的方式分给左右两个区间 +所以说 cdq 分治只是一种十分模糊的思想,可以看到这种思想就是不断的把点对通过递归~~(甩锅)~~的方式分给左右两个区间 至于我们设计出来的算法真正干活的部分就是第 4 部分需要我们想方设法解决的部分了 @@ -48,39 +48,39 @@ _所以刚才的算法流程中的递归部分我们就是通过 $solve(l,mid),s ### 三维偏序 -给定一个序列, 每个点有两个属性 $(a,b)$,试求:这个序列里有多少对点对$(i,j)$满足$i @@ -354,22 +354,21 @@ int main() 众所周知的是有些数据结构题需要我们兹次做 xxx 修改然后做 xxx 询问的情况 -然后你会发现一个有趣的事实是如果我们把询问进行离线之后,所有操作按照时间自然的排成了一个序列,另一个比较显然的事实是每一个修改会对它之后的询问发生关系,而这样的修改 - 询问关系一共会有$O(n^2)$对 +然后你会发现一个有趣的事实是如果我们把询问进行离线之后,所有操作按照时间自然的排成了一个序列,另一个比较显然的事实是每一个修改会对它之后的询问发生关系,而这样的修改 - 询问关系一共会有 $O(n^2)$ 对 -因此我们可以使用 cdq 分治对于这个操作序列进行分治, 按照 cdq 分治处理修改和询问之间的关系 +因此我们可以使用 cdq 分治对于这个操作序列进行分治,按照 cdq 分治处理修改和询问之间的关系 -还是和处理点对关系的 cdq 分治类似, 我们假设我们正在分治的序列是$(l,r)$, 我们先递归的处理$(l,mid)$和$(mid,r)$之间的修改 - 询问关系 +还是和处理点对关系的 cdq 分治类似,我们假设我们正在分治的序列是 $(l,r)$ , 我们先递归的处理 $(l,mid)$ 和 $(mid,r)$ 之间的修改 - 询问关系 -接下来我们处理所有$l \leq i \leq mid,mid+1 \leq j \leq r$并且$i$是一个修改并且$j$是一个询问的修改 - 询问关系 +接下来我们处理所有 $l \leq i \leq mid,mid+1 \leq j \leq r$ 并且 $i$ 是一个修改并且 $j$ 是一个询问的修改 - 询问关系 -注意如果我们的各个修改之间是**独立**的话我们不需要管处理$l \leq i \leq mid,mid+1 \leq j \leq r$和$solve(l,mid)$以及$solve(mid+1,r)$ -之间时序关系 (比如你的修改就是普通的加法和减法问题之类的) +注意如果我们的各个修改之间是**独立**的话我们不需要管处理 $l \leq i \leq mid,mid+1 \leq j \leq r$ 和 $solve(l,mid)$ 以及 $solve(mid+1,r)$ 之间时序关系(比如你的修改就是普通的加法和减法问题之类的) 但是如果你的各个修改之间并不独立,比如说我们的修改是一个赋值操作,这样的话我们做完这个赋值操作之后序列长什么样可能需要依赖于之前的序列长什么样 -那这样的话我们处理所有跨越 mid 的修改 - 询问关系的时候就必须把它放在$solve(l,mid)$和$solve(mid+1,r)$之间了, 理由和 cdq 分治优化 1D/1D 动态规划的原因是一样的, 按照中序遍历序进行分治,然后我们就可以保证每一个修改都是严格按照时间顺序被执行的 +那这样的话我们处理所有跨越 mid 的修改 - 询问关系的时候就必须把它放在 $solve(l,mid)$ 和 $solve(mid+1,r)$ 之间了,理由和 cdq 分治优化 1D/1D 动态规划的原因是一样的,按照中序遍历序进行分治,然后我们就可以保证每一个修改都是严格按照时间顺序被执行的 -这样光说是没办法解决我们的问题的, 因此我们还是上道例题吧 +这样光说是没办法解决我们的问题的,因此我们还是上道例题吧 ### 矩形加矩形求和 @@ -383,25 +382,25 @@ int main() 不如强行套一个 cdq 分治试试? -我们将所有的询问和修改操作全部离线, 这些操作形成了一个序列,并且有$O(N^2)$对修改 - 询问的关系 +我们将所有的询问和修改操作全部离线,这些操作形成了一个序列,并且有 $O(N^2)$ 对修改 - 询问的关系 -那么我们依然使用 cdq 分治的一般套路, 将所有的关系分成三类, 在这一层分治过程当中仅仅处理跨越$mid$,的修改 - 询问关系,而剩下的修改 - 询问关系通过递归的的方式来解决 +那么我们依然使用 cdq 分治的一般套路,将所有的关系分成三类,在这一层分治过程当中仅仅处理跨越 $mid$ ,的修改 - 询问关系,而剩下的修改 - 询问关系通过递归的的方式来解决 那么这样的话我们会发现这样的一个事实就是所有的修改都在询问之前被做出了 这个问题就等价于平面上有静态的一堆矩形接下来不停的询问一个矩形区域的和了 -那么我们可以套一个扫描线在$O(nlogn)$的时间内处理好所有跨越$mid$的修改 - 询问关系 +那么我们可以套一个扫描线在 $O(nlogn)$ 的时间内处理好所有跨越 $mid$ 的修改 - 询问关系 剩下的事情就是递归的分治左右两侧修改 - 询问关系来解决这个问题了 -这样实现的 cdq 分治的话你会发现同一个询问被处理了$O(logn)$次来回答, 不过没有关系因为每次贡献这个询问的修改是互不相交的 +这样实现的 cdq 分治的话你会发现同一个询问被处理了 $O(logn)$ 次来回答,不过没有关系因为每次贡献这个询问的修改是互不相交的 -时间复杂度为$T(n)=T(\lfloor \frac{n}{2} \rfloor)+T(\lceil \frac{n}{2} \rceil)+ O(nlogn)=O(nlog^2n)$ +时间复杂度为 $T(n)=T(\lfloor \frac{n}{2} \rfloor)+T(\lceil \frac{n}{2} \rceil)+ O(nlogn)=O(nlog^2n)$ -观察上述的算法流程,我们发现一开始我们只能解决静态的矩形加矩形求和问题, 但是只是简单的套了一个 cdq 分治上去我们就可以离线的解决一个动态的矩形加矩形求和问题了。 +观察上述的算法流程,我们发现一开始我们只能解决静态的矩形加矩形求和问题,但是只是简单的套了一个 cdq 分治上去我们就可以离线的解决一个动态的矩形加矩形求和问题了。 -那么我们可以将动态问题转化为静态问题的精髓就在于 cdq 分治每次仅仅处理跨越某一个点的修改和询问关系了, 这样的话我们就只需要考虑所有询问都在修改之后这个简单的问题了。 +那么我们可以将动态问题转化为静态问题的精髓就在于 cdq 分治每次仅仅处理跨越某一个点的修改和询问关系了,这样的话我们就只需要考虑所有询问都在修改之后这个简单的问题了。 也正是因为这一点 cdq 分治被称为**动态问题转化为静态问题的工具** @@ -409,11 +408,11 @@ int main() 一句话题意区间赋值区间数颜色 -我们维护一下每个位置左侧第一个同色点的位置, 记为$pre_{i}$,此时区间数颜色就被转化为了一个经典的二维数点问题 +我们维护一下每个位置左侧第一个同色点的位置,记为 $pre_{i}$ ,此时区间数颜色就被转化为了一个经典的二维数点问题 -通过将连续的一段颜色看成一个点的方式我们可以证明$pre$的变化量是$O(n+m)$的, 换句话说单次操作仅仅引起$O(1)$的$pre$值变化,那么我们可以用 cdq 分治来解决动态的单点加矩形求和问题 +通过将连续的一段颜色看成一个点的方式我们可以证明 $pre$ 的变化量是 $O(n+m)$ 的,换句话说单次操作仅仅引起 $O(1)$ 的 $pre$ 值变化,那么我们可以用 cdq 分治来解决动态的单点加矩形求和问题 -$pre$数组的具体变化可以使用$std::set$来进行处理 (这个用 set 维护连续的区间的技巧也被称之为 _old driver tree_ ) + $pre$ 数组的具体变化可以使用 $std::set$ 来进行处理(这个用 set 维护连续的区间的技巧也被称之为_old driver tree_) ```C #include @@ -533,51 +532,51 @@ int main(){prew::prew();cdq::mainsolve();return 0;}//拜拜程序~ ### [HNOI2010]城市建设 -一句话题意: 给定一张图支持动态的修改边权, 要求在每次修改边权之后输出这张图的最小生成树的最小代价和 +一句话题意:给定一张图支持动态的修改边权,要求在每次修改边权之后输出这张图的最小生成树的最小代价和 -事实上有一个线段树分治套 lct 的做法可以解决这个问题, 但是这个实现方式常数过大可能需要精妙的卡常技巧才可以通过本题, 因此我们不妨考虑 cdq 分治来解决这个问题 +事实上有一个线段树分治套 lct 的做法可以解决这个问题,但是这个实现方式常数过大可能需要精妙的卡常技巧才可以通过本题,因此我们不妨考虑 cdq 分治来解决这个问题 -和一般的 cdq 分治解决的问题不同,我们此时 cdq 分治的时候并没有修改和询问的关系来让我们进行分治, 因为我们是没有办法单独的考虑修改一个边对整张图的最小生成树有什么贡献, 因此似乎传统的 cdq 分治思路似乎不是很好使 +和一般的 cdq 分治解决的问题不同,我们此时 cdq 分治的时候并没有修改和询问的关系来让我们进行分治,因为我们是没有办法单独的考虑修改一个边对整张图的最小生成树有什么贡献,因此似乎传统的 cdq 分治思路似乎不是很好使 -那么我们通过刚才的例题可以发现一般的 cdq 分治和线段树有着特殊的联系,我们在 cdq 分治的过程中其实隐式的建了一颗线段树出来 (因为 cdq 分治的递归树就是一颗线段树) +那么我们通过刚才的例题可以发现一般的 cdq 分治和线段树有着特殊的联系,我们在 cdq 分治的过程中其实隐式的建了一颗线段树出来(因为 cdq 分治的递归树就是一颗线段树) 通常的 cdq 是考虑线段树左右儿子之间的联系 而对于这道题来讲我们需要考虑的是父亲和孩子之间的关系 -换句话来讲, 我们在$solve(l,r)$这段区间的时候如果我们可以想办法使图的规模变成和区间长度相关的一个变量的话我们就可以解决这个问题了 +换句话来讲,我们在 $solve(l,r)$ 这段区间的时候如果我们可以想办法使图的规模变成和区间长度相关的一个变量的话我们就可以解决这个问题了 -那么具体来讲如何设计算法呢? +那么具体来讲如何设计算法呢? -假设我们正在构造$(l,r)$这段区间的最小生成树边集,并且我们已知它父亲最小生成树的边集 +假设我们正在构造 $(l,r)$ 这段区间的最小生成树边集,并且我们已知它父亲最小生成树的边集 -我们将在$(l,r)$这段区间中发生变化的边分别将边权赋成$+ \infty$和$-\infty$分别各跑一边 kruskal 求出那些边在最小生成树当中 +我们将在 $(l,r)$ 这段区间中发生变化的边分别将边权赋成 $+ \infty$ 和 $-\infty$ 分别各跑一边 kruskal 求出那些边在最小生成树当中 -对于一条边来讲, 如果他没有出现在了所有被修改的边权都被赋成了$+\infty$的最小生成树当中证明它不可能出现在$(l,r)$这些询问的最小生成树当中, 所以我们仅仅在$(l,r)$的边集中加入最小生成树的树边 +对于一条边来讲,如果他没有出现在了所有被修改的边权都被赋成了 $+\infty$ 的最小生成树当中证明它不可能出现在 $(l,r)$ 这些询问的最小生成树当中,所以我们仅仅在 $(l,r)$ 的边集中加入最小生成树的树边 -对于一条边来讲, 如果它出现在了所有被修改的边权都被赋成了$- \infty$的最小生成树当中,就证明它一定会出现$(l,r)$这段的区间的最小生成树当中, 这样的话我们就可以使用并查集将这些边对应的点缩起来, 并且将答案加上这些边的边权 +对于一条边来讲,如果它出现在了所有被修改的边权都被赋成了 $- \infty$ 的最小生成树当中,就证明它一定会出现 $(l,r)$ 这段的区间的最小生成树当中,这样的话我们就可以使用并查集将这些边对应的点缩起来,并且将答案加上这些边的边权 -如此这般我们就将$(l,r)$这段区间的边集构造出来了, 用这些边求出来的最小生成树和直接求原图的最小生成树等价 +如此这般我们就将 $(l,r)$ 这段区间的边集构造出来了,用这些边求出来的最小生成树和直接求原图的最小生成树等价 -那么为什么我们的复杂度是对的呢? +那么为什么我们的复杂度是对的呢? -首先被修改的边一定会加入到我们的边集当中去, 这些边的数目是$O(len)$级别的 +首先被修改的边一定会加入到我们的边集当中去,这些边的数目是 $O(len)$ 级别的 接下来我们需要证明的是边集当中不会有过多的未被修改的边 -注意到我们只会加入所有边权取$+\infty$最小生成树的树边, 因此我们加入的边数目是不会超过当前图的点数的 +注意到我们只会加入所有边权取 $+\infty$ 最小生成树的树边,因此我们加入的边数目是不会超过当前图的点数的 -接下来我们只需证明每递归一层图的点数是$O(len)$级别的就可以说明图的边数是$O(len)$级别的了 +接下来我们只需证明每递归一层图的点数是 $O(len)$ 级别的就可以说明图的边数是 $O(len)$ 级别的了 -证明点数是$O(len)$几倍就变的十分简单了, 我们每次向下递归的时侯缩掉的边是在$-\infty$生成树中出现的未被修改边, 那么反过来想就是我们割掉了出现在$-\infty$生成树当中的所有的被修改边, 显然我们最多割掉$len$条边,整张图最多分裂成$O(len)$个联通块, 这样的话新图点数就是$O(len)$级别的了 +证明点数是 $O(len)$ 几倍就变的十分简单了,我们每次向下递归的时侯缩掉的边是在 $-\infty$ 生成树中出现的未被修改边,那么反过来想就是我们割掉了出现在 $-\infty$ 生成树当中的所有的被修改边,显然我们最多割掉 $len$ 条边,整张图最多分裂成 $O(len)$ 个联通块,这样的话新图点数就是 $O(len)$ 级别的了 -所以我们就证明了每次我们用来跑 kruskal 的图都是$O(len)$级别的了 +所以我们就证明了每次我们用来跑 kruskal 的图都是 $O(len)$ 级别的了 -从而每一层的时间复杂度都是$(nlogn)$了 +从而每一层的时间复杂度都是 $(nlogn)$ 了 -因此我们的时间复杂度就是$T(n)=T(\lfloor \frac{n}{2} \rfloor)+T(\lceil \frac{n}{2} \rceil)+ O(nlogn)=O(nlog^2n)$了 +因此我们的时间复杂度就是 $T(n)=T(\lfloor \frac{n}{2} \rfloor)+T(\lceil \frac{n}{2} \rceil)+ O(nlogn)=O(nlog^2n)$ 了 -代码实现上可能会有一些难度, 需要注意的是并查集不能使用路径压缩, 否则就不支持回退操作了, 执行缩点操作的时候也没有必要真的执行,而是每一层的 kruskal 都在上一层的并查集里直接做就可以了 +代码实现上可能会有一些难度,需要注意的是并查集不能使用路径压缩,否则就不支持回退操作了,执行缩点操作的时候也没有必要真的执行,而是每一层的 kruskal 都在上一层的并查集里直接做就可以了 ```C #include diff --git a/docs/misc/complexity.md b/docs/misc/complexity.md index 80d2056b..7b00ed27 100644 --- a/docs/misc/complexity.md +++ b/docs/misc/complexity.md @@ -1,32 +1,32 @@ -复杂度是我们衡量一个算法好坏的重要的标准。在算法竞赛中,我们通常关注于算法的时间复杂和空间复杂度。 +复杂度是我们衡量一个算法好坏的重要的标准。在算法竞赛中,我们通常关注于算法的时间复杂和空间复杂度。 -一般的来说,复杂度是一个关于输入长度的一个函数。对于某些算法来说,相同的输入的不同输入依然会造成算法的运行时间 / 空间的不同,因此我们通常使用算法的最坏时间复杂度,记为 $T(n)$ 。对于一些特殊的情况,我们可能会关心它的平均情况复杂复杂度(特别是对于随机算法 (randomized algorithm) ),这个时候我们通过使用随机分析 (probabilistic analysis) 来得到期望的复杂度。 +一般的来说,复杂度是一个关于输入长度的一个函数。对于某些算法来说,相同的输入的不同输入依然会造成算法的运行时间/空间的不同,因此我们通常使用算法的最坏时间复杂度,记为 $T(n)$ 。对于一些特殊的情况,我们可能会关心它的平均情况复杂复杂度(特别是对于随机算法 (randomized algorithm)),这个时候我们通过使用随机分析 (probabilistic analysis) 来得到期望的复杂度。 ## 渐进符号 我们通常使用渐进符号来描述一个算法的复杂度。 -### $\Theta$ 符号 +### $\Theta$ 符号 -对于给定的一个函数 $g(n)$, 函数集合 $\Theta(g(n))$ 定义为 +对于给定的一个函数 $g(n)$ , 函数集合 $\Theta(g(n))$ 定义为 $$ \Theta(g(n)) = \{f(n) : 存在常数 c_1,c_2,n_0 \in \mathbb{R^{+}}使得 0 \leq c_1g(n) \leq f(n) \leq c_2g(n), \qquad \forall n \geq n_0\} $$ -也就是说,如果函数 $f(n)$ 属于 $\Theta(g(n))$,那么我们能找到两个正常数 $c_1, c_2$ 使得 $f(n)$ 被 $c_1g(n)$ 和 $c_2g(n)$ 夹在中间。 因为 $\Theta(g(n))$ 是一个函数集合,我们可以用 $f(n) \in \Theta(g(n))$ 表达 $f(n)$ 属于 $\Theta(g(n))$, 但是我们通常使用 $f(n) = \Theta(g(n))$。 +也就是说,如果函数 $f(n)$ 属于 $\Theta(g(n))$ ,那么我们能找到两个正常数 $c_1, c_2$ 使得 $f(n)$ 被 $c_1g(n)$ 和 $c_2g(n)$ 夹在中间。因为 $\Theta(g(n))$ 是一个函数集合,我们可以用 $f(n) \in \Theta(g(n))$ 表达 $f(n)$ 属于 $\Theta(g(n))$ ,但是我们通常使用 $f(n) = \Theta(g(n))$ 。 -### $O$ 符号 +### $O$ 符号 -$\Theta$ 符号同时给了我们一个函数的上下界,如果我们只有一个函数的渐进上界的时候,我们使用$O$ 符号。 对于一个给定的函数 $g(n)$, 我们把它记作 $O(g(n))$。 + $\Theta$ 符号同时给了我们一个函数的上下界,如果我们只有一个函数的渐进上界的时候,我们使用 $O$ 符号。对于一个给定的函数 $g(n)$ , 我们把它记作 $O(g(n))$ 。 $$ O(g(n)) = \{f(n):存在常数 c,n_0 使得 0\leq f(n) \leq cg(n), \qquad \forall n \geq n_0\} $$ -### $\Omega$ 符号 +### $\Omega$ 符号 -同样的,我们使用$\Omega$符号来描述一个函数的渐进下界。 +同样的,我们使用 $\Omega$ 符号来描述一个函数的渐进下界。 $$ \Omega(g(n)) = \{f(n):存在常数 c,n_0 使得 0 \leq cg(n) \leq f(n) , \qquad \forall n \geq n_0\} @@ -36,9 +36,9 @@ $$ ### 常见性质 -- $f_1(n) + f_2(n) = O(\max(f_1(n), f_2(n)))$ -- $f_1(n) \times f_2(n) = O(f_1(n) \times f_2(n))$ -- 任何对数函数无论底数为何,都具有相同的增长率。$\forall a \neq 1, \log_a{n} = O(\log_2 n)$ +- $f_1(n) + f_2(n) = O(\max(f_1(n), f_2(n)))$ +- $f_1(n) \times f_2(n) = O(f_1(n) \times f_2(n))$ +- 任何对数函数无论底数为何,都具有相同的增长率。 $\forall a \neq 1, \log_a{n} = O(\log_2 n)$ ## 主定理 (Master Theorem) @@ -59,8 +59,8 @@ $$ 算法往往是会对内存中的数据进行修改的,而同一个算法的多次执行,就会通过对数据的修改而互相影响。 -例如快速排序中的 “按大小分类” 操作,单次执行的最坏时间复杂度,看似是$O(n)$的。 -但是由于快排的分治过程,先前的 “分类” 操作每次都减小了数组长度,所以实际的总复杂度$O(n \log_2 n)$,分摊在每一次 “分类” 操作上,是$O(\log_2 n)$。 +例如快速排序中的“按大小分类”操作,单次执行的最坏时间复杂度,看似是 $O(n)$ 的。 +但是由于快排的分治过程,先前的“分类”操作每次都减小了数组长度,所以实际的总复杂度 $O(n \log_2 n)$ ,分摊在每一次“分类”操作上,是 $O(\log_2 n)$ 。 多次操作的总复杂度除以操作次数,就是这种操作的**均摊复杂度**。 @@ -69,17 +69,15 @@ $$ 势能分析,是一种求均摊复杂度下界的方法。 求均摊复杂度,关键是表达出先前操作对当前操作的影响。势能分析用一个函数来表达此种影响。 -定义 “状态”$S$:即某一时刻的所有数据。 -_在快排的例子中,一个 “状态” 就是当前过程需要排序的下标区间_ +定义“状态” $S$ :即某一时刻的所有数据。_在快排的例子中,一个“状态”就是当前过程需要排序的下标区间_ -定义 “初始状态”$S_0$:即未进行任何操作时的状态。 -_在快排的例子中,“初始状态” 就是整个数组_ +定义“初始状态” $S_0$ :即未进行任何操作时的状态。_在快排的例子中,“初始状态”就是整个数组_ -假设存在从状态到数的函数$F$,且对于任何状态$S$,$F(S) \geq F(S_0)$,则有以下推论: +假设存在从状态到数的函数 $F$ ,且对于任何状态 $S$ , $F(S) \geq F(S_0)$ ,则有以下推论: -设$S_1,S_2, \cdots ,S_m$为从$S_0$开始连续做$m$次操作所得的状态序列,$c_i$为第$i$次操作的时间开销。 +设 $S_1,S_2, \cdots ,S_m$ 为从 $S_0$ 开始连续做 $m$ 次操作所得的状态序列, $c_i$ 为第 $i$ 次操作的时间开销。 -记$p_i = c_i + F(S_i) - F(S_{i-1})$,则$m$次操作的总时间花销为 +记 $p_i = c_i + F(S_i) - F(S_{i-1})$ ,则 $m$ 次操作的总时间花销为 $$ \sum_{i=1}^m p_i + F(S_0) - F(S_m) @@ -87,12 +85,12 @@ $$ (正负相消,证明显然) -又因为$F(S) \geq F(S_0)$,所以有 +又因为 $F(S) \geq F(S_0)$ ,所以有 $$ \sum_{i=1}^m p_i \geq \sum_{i=1}^m c_i $$ -因此,若$p_i = O(T(n))$,则$O(T(n))$是均摊复杂度的一个下界。 +因此,若 $p_i = O(T(n))$ ,则 $O(T(n))$ 是均摊复杂度的一个下界。 势能分析使用中有很多技巧,案例在此不题。 diff --git a/docs/misc/discrete.md b/docs/misc/discrete.md index b7e7264e..b4b7f77d 100644 --- a/docs/misc/discrete.md +++ b/docs/misc/discrete.md @@ -4,9 +4,9 @@ 或者是有些数本身很大,自身无法作为数组的下标来方便地处理。 -用来离散化的可以是大整数、浮点数、字符串 …… 等等。 +用来离散化的可以是大整数、浮点数、字符串……等等。 -离散化本质上也可以看成是 [哈希](/string/hash) 的过程。 +离散化本质上也可以看成是[哈希](/string/hash)的过程。 ## 实现 diff --git a/docs/misc/distance.md b/docs/misc/distance.md index 89661f49..f8657a38 100644 --- a/docs/misc/distance.md +++ b/docs/misc/distance.md @@ -2,33 +2,33 @@ ### 曼哈顿距离 -对于平面上两点 $A(x_1, y_1)$, $B(x_2, y_2)$ $Dis(A, B) = |x_1-x_2| + |y_1-y_2|$ +对于平面上两点 $A(x_1, y_1)$ , $B(x_2, y_2)$ $Dis(A, B) = |x_1-x_2| + |y_1-y_2|$ 一般来讲,我们只会用到二维平面上的曼哈顿距离 ### 切比雪夫距离 -对于平面上两点 $A(x_1, y_1)$, $B(x_2, y_2)$ $Dis(A, B) = \max(|x_1-x_2| , |y_1-y_2|)$ +对于平面上两点 $A(x_1, y_1)$ , $B(x_2, y_2)$ $Dis(A, B) = \max(|x_1-x_2| , |y_1-y_2|)$ -对于两个 $n$ 维向量 $\vec A(x_{11}, x_{12}, \cdots,x_{1n})$ $\vec B(x_{21}, x_{22}, \cdots,x_{2n})$ +对于两个 $n$ 维向量 $\vec A(x_{11}, x_{12}, \cdots,x_{1n})$ $\vec B(x_{21}, x_{22}, \cdots,x_{2n})$ -$Dis(A, B) = \max\limits_i(|x_{1i}-x_{2i}|)$ + $Dis(A, B) = \max\limits_i(|x_{1i}-x_{2i}|)$ -### 欧几里得距离 (又称欧氏距离) +### 欧几里得距离(又称欧氏距离) 欧几里得距离是两点的直线距离。 -对于平面上两点 $A(x_1, y_1)$, $B(x_2, y_2)$ $Dis(A, B) = \sqrt{(x_1-x2)^2+(y1-y2)^2}$ +对于平面上两点 $A(x_1, y_1)$ , $B(x_2, y_2)$ $Dis(A, B) = \sqrt{(x_1-x2)^2+(y1-y2)^2}$ -对于两个 $n$ 维向量 $\vec A(x_{11}, x_{12}, \cdots,x_{1n})$ $\vec B(x_{21}, x_{22}, \cdots,x_{2n})$ +对于两个 $n$ 维向量 $\vec A(x_{11}, x_{12}, \cdots,x_{1n})$ $\vec B(x_{21}, x_{22}, \cdots,x_{2n})$ -$Dis(A, B) = \sqrt{\sum\limits_{k=1}^n(x_{1k}-x_{2k})^2}$ + $Dis(A, B) = \sqrt{\sum\limits_{k=1}^n(x_{1k}-x_{2k})^2}$ -### $L_m$ 距离 +### $L_m$ 距离 -一般地,我们定义平面上两点 $A(x_1, y_1)$, $B(x_2, y_2)$ 之间的 $L_m$ 距离为 +一般地,我们定义平面上两点 $A(x_1, y_1)$ , $B(x_2, y_2)$ 之间的 $L_m$ 距离为 -$dist~L_m = (|x_1-x_2|^m+|y1-y2|^m)^{\frac{1}{m}}$ + $dist~L_m = (|x_1-x_2|^m+|y1-y2|^m)^{\frac{1}{m}}$ 特殊的, $L_2$ 距离就是欧几里得距离, $L_1$ 距离就是曼哈顿距离。 @@ -36,8 +36,8 @@ $dist~L_m = (|x_1-x_2|^m+|y1-y2|^m)^{\frac{1}{m}}$ 汉明距离是两个字符串之间的距离,它表示两个长度相同的字符串对应位字符不同的数量 -我们可以简单的认为对两个串进行异或运算, 结果为 1 的数量就是两个串的汉明距离。 +我们可以简单的认为对两个串进行异或运算,结果为 1 的数量就是两个串的汉明距离。 * * * -当然, 还有其他的一些距离,但是在 OI 中并不常用,有兴趣的话可以了解一下。 +当然,还有其他的一些距离,但是在 OI 中并不常用,有兴趣的话可以了解一下。 diff --git a/docs/misc/dsu-on-tree.md b/docs/misc/dsu-on-tree.md index cb024e73..8bcca73e 100644 --- a/docs/misc/dsu-on-tree.md +++ b/docs/misc/dsu-on-tree.md @@ -31,23 +31,23 @@ void merge(int x, int y) { 给出一棵树,每个节点有颜色,询问一些子树的颜色数量(颜色可重复)。 -![24620.png](./images/24620.png) +![24620.png](./images/24620.png) 对于这种问题解决方式大多是运用大量的数据结构(树套树等),如果可以离线,询问的量巨大,是不是有更简单的方法? -树上莫队! +树上莫队! 不行,莫队带根号,我要 log 既然支持离线,考虑预处理后 $O(1)$ 输出答案。 -直接暴力预处理的时间复杂度为 $O(n^2)$,即对每一个子节点进行一次遍历,每次遍历的复杂度显然与 $n$ 同阶,有 $n$ 个节点,故复杂度为 $O(n^2)$。 +直接暴力预处理的时间复杂度为 $O(n^2)$ ,即对每一个子节点进行一次遍历,每次遍历的复杂度显然与 $n$ 同阶,有 $n$ 个节点,故复杂度为 $O(n^2)$ 。 可以发现,每个节点的答案是其子树的叠加,考虑利用这个性质处理问题。 我们可以先预处理出每个节点子树的 $size$ 和它的重儿子,重儿子同树链剖分一样,是拥有节点最多子树的儿子,这个过程显然可以 $O(n)$ 完成 -我们用 check[i] 表示颜色$i$有没有出现过,ans[i] 表示他的颜色个数 +我们用 check[i]表示颜色 $i$ 有没有出现过,ans[i]表示他的颜色个数 遍历一个节点,我们按以下的步骤进行遍历: @@ -57,7 +57,7 @@ void merge(int x, int y) { - 再次遍历其非重儿子及其父亲,用重儿子的 check 对遍历到的节点进行计算,获取整棵子树的 ans; -![24919.png](./images/24919.png) +![24919.png](./images/24919.png) _上图是一个例子_ @@ -77,13 +77,13 @@ _上图是一个例子_ 我们像树链剖分一样定义重边和轻边(连向重儿子的为重边,其余为轻边)关于重儿子和重边的定义,可以见下图,对于一棵有 $n$ 个节点的树: -根节点到树上任意节点的轻边数不超过 $\log n$ 条。我们设根到该节点有 x 条轻边该节点的子树大小为 $y$,显然轻边连接的子节点的子树大小小于父亲的一半(若大于一半就不是轻边了),则 $y2^x$,所以 $x<\log n$。 +根节点到树上任意节点的轻边数不超过 $\log n$ 条。我们设根到该节点有 x 条轻边该节点的子树大小为 $y$ ,显然轻边连接的子节点的子树大小小于父亲的一半(若大于一半就不是轻边了),则 $y2^x$ ,所以 $x<\log n$ 。 -又因为如果一个节点是其父亲的重儿子,则他的子树必定在他的兄弟之中最多,所以任意节点到根的路径上所有重边连接的父节点在计算答案是必定不会遍历到这个节点,所以一个节点的被遍历的次数等于他到根节点路径上的轻边树 $+1$(之所以要 $+1$ 是因为他本身要被遍历到),所以一个节点的被遍历次数 $=\log n+1$, 总时间复杂度则为 $O(n(\log n+1))=O(n\log n)$,输出答案花费 $O(m)$. +又因为如果一个节点是其父亲的重儿子,则他的子树必定在他的兄弟之中最多,所以任意节点到根的路径上所有重边连接的父节点在计算答案是必定不会遍历到这个节点,所以一个节点的被遍历的次数等于他到根节点路径上的轻边树 $+1$ (之所以要 $+1$ 是因为他本身要被遍历到),所以一个节点的被遍历次数 $=\log n+1$ , 总时间复杂度则为 $O(n(\log n+1))=O(n\log n)$ ,输出答案花费 $O(m)$ . -![24909.png](./images/24909.png) +![24909.png](./images/24909.png) - _图中标红的即为重边,重边连向的子节点为重儿子_ +_图中标红的即为重边,重边连向的子节点为重儿子_ ### 大致代码 @@ -137,11 +137,11 @@ int dfs2(int u, int fa, bool keep, bool isson) { 1. 某些出题人设置的正解是 dsu on tree 的题 -如 [CF741D](http://codeforces.com/problemset/problem/741/D)。给一棵树,每个节点的权值是'a'到'v'的字母,每次询问要求在一个子树找一条路径,使该路径包含的字符排序后成为回文串。 +如[CF741D](http://codeforces.com/problemset/problem/741/D)。给一棵树,每个节点的权值是'a'到'v'的字母,每次询问要求在一个子树找一条路径,使该路径包含的字符排序后成为回文串。 因为是排列后成为回文串,所以一个字符出现了两次相当于没出现,也就是说,这条路径满足**最多有一个字符出现奇数次**。 -正常做法是对每一个节点 dfs,每到一个节点就强行枚举所有字母找到和他异或后结果为 1 的个数 < 1 的路径,再去最长值,这样 $O(n^2\log n)$ 的,可以用 dsu on tree 优化到 $O(n\log^2n)$。关于具体做法,可以参考下面的扩展阅读 +正常做法是对每一个节点 dfs,每到一个节点就强行枚举所有字母找到和他异或后结果为 1 的个数<1 的路径,再去最长值,这样 $O(n^2\log n)$ 的,可以用 dsu on tree 优化到 $O(n\log^2n)$ 。关于具体做法,可以参考下面的扩展阅读 2. 可以用 dsu 乱搞~~吊打 std~~水分的题 @@ -155,7 +155,7 @@ int dfs2(int u, int fa, bool keep, bool isson) { [UOJ284 快乐游戏鸡](http://uoj.ac/problem/284) -### 参考资料 / 扩展阅读 +### 参考资料/扩展阅读 [CF741D 作者介绍的 dsu on tree](http://codeforces.com/blog/entry/44351) diff --git a/docs/misc/endianness.md b/docs/misc/endianness.md index 5918cb17..34e219ed 100644 --- a/docs/misc/endianness.md +++ b/docs/misc/endianness.md @@ -2,17 +2,17 @@ 字节顺序有两种,分为小端序(small endian)和大端序(big endian)。 -事实上,这两种字节顺序没有孰优孰劣之分。这两种顺序的名字(`小端` 和 `大端`),正是出自 《格列佛游记》一书,书中的两个派别交战的原因是无法就从哪一端打开鸡蛋达成一致。就和鸡蛋的问题一样,选择何种字节顺序成了没有技术上理由的争论。 +事实上,这两种字节顺序没有孰优孰劣之分。这两种顺序的名字( `小端` 和 `大端` ),正是出自《格列佛游记》一书,书中的两个派别交战的原因是无法就从哪一端打开鸡蛋达成一致。就和鸡蛋的问题一样,选择何种字节顺序成了没有技术上理由的争论。 当然,字节顺序的不一致会导致二进制数据在不同类型的机器之间进行传输时被反序。为了避免这件事情,网络应用程序建立了一套标准,保证发送过程中是使用约定好的网络标准,而不是不同机器的内部表示。 -下面,我们以一个位于 `0x100` 处,类型为 `int`,十六进制值为 `0x01234567` 的变量为例介绍两种字节顺序: +下面,我们以一个位于 `0x100` 处,类型为 `int` ,十六进制值为 `0x01234567` 的变量为例介绍两种字节顺序: -(这里 `0x01` 是最高位有效字节,`0x67` 是最低位有效字节) +(这里 `0x01` 是最高位有效字节, `0x67` 是最低位有效字节) ## 小端序 -小端序是指机器选择在内存中按照从 **最低** 有效字节到 **最高** 有效字节的顺序存储对象。 +小端序是指机器选择在内存中按照从**最低**有效字节到**最高**有效字节的顺序存储对象。 上文提到的变量就表示如下: @@ -22,7 +22,7 @@ ## 大端序 -大端序是指机器选择在内存中按照从 **最高** 有效字节到 **最低** 有效字节的顺序存储对象。 +大端序是指机器选择在内存中按照从**最高**有效字节到**最低**有效字节的顺序存储对象。 上文提到的变量就表示如下: diff --git a/docs/misc/fractional-programming.md b/docs/misc/fractional-programming.md index 8673899a..34f7ad98 100644 --- a/docs/misc/fractional-programming.md +++ b/docs/misc/fractional-programming.md @@ -1,4 +1,4 @@ -!!! note " 例题 [ luogu P4322 \[JSOI2016\] 最佳团体 ](https://www.luogu.org/problemnew/show/P4322)" +!!! note " 例题[luogu P4322\[JSOI2016\]最佳团体](https://www.luogu.org/problemnew/show/P4322)" 题目大意:有一棵 $n+1$ 个结点的树,根为 $0$ 号结点。每个结点 $i$ 有一个价值 $p_i$ 和费用 $s_i$ 。你需要选择 $k$ 个结点 $a_1,a_2,\ldots,a_k$ (不包括 $0$ 号结点),使得 $$ @@ -20,13 +20,13 @@ f(i,j)=\left\{ . $$ -如果 ** 合并方式得当 ** ,则可以在 $O(n^2)$ 的时间复杂度内完成状态转移,具体细节参见代码。 +如果**合并方式得当**,则可以在 $O(n^2)$ 的时间复杂度内完成状态转移,具体细节参见代码。 但是,我们是让一个分式的值最大化,然而这个分式的分母又不确定,怎么办呢? 首先我们明确一个性质:设最后最大化的答案为 $ans$ ,那么对于任意一个小于等于 $ans$ 的实数 $a$ ,都有 $v_i=p_i-a\times s_i$ ,这样的 $v$ 数组算出的 $f(0,k+1)$ 的值都大于零。该式等于零当且仅当 $a=ans$ 。 -那么,我们就可以用二分的思想解决这个问题。每次选择一个 $mid$ ,令 $v_i=p_i-mid\times s_i$ , 算出 $f(0,k+1)$ 的值,如果大于零则选择右区间,反之选择左区间。最后当区间 $[l,r]$ 的大小 $r-l$ 在可以容许的范围内时,输出最后的答案。时间复杂度为 $O(n^2\log ans)$ 。 +那么,我们就可以用二分的思想解决这个问题。每次选择一个 $mid$ ,令 $v_i=p_i-mid\times s_i$ ,算出 $f(0,k+1)$ 的值,如果大于零则选择右区间,反之选择左区间。最后当区间 $[l,r]$ 的大小 $r-l$ 在可以容许的范围内时,输出最后的答案。时间复杂度为 $O(n^2\log ans)$ 。 这就是分数规划的基本思想。 @@ -84,8 +84,8 @@ int main() { ## 习题 -[ luogu P3705 \[SDOI2017\]新生舞会 ](https://www.luogu.org/problemnew/show/P3705) +[luogu P3705\[SDOI2017\]新生舞会](https://www.luogu.org/problemnew/show/P3705) -[ luogu P3288 \[SCOI2014\]方伯伯运椰子 ](https://www.luogu.org/problemnew/show/P3288) +[luogu P3288\[SCOI2014\]方伯伯运椰子](https://www.luogu.org/problemnew/show/P3288) -[ luogu P3199 \[HNOI2009\]最小圈 ](https://www.luogu.org/problemnew/show/P3199) +[luogu P3199\[HNOI2009\]最小圈](https://www.luogu.org/problemnew/show/P3199) diff --git a/docs/misc/hill-climbing.md b/docs/misc/hill-climbing.md index ee33e860..dea8af96 100644 --- a/docs/misc/hill-climbing.md +++ b/docs/misc/hill-climbing.md @@ -10,7 +10,7 @@ 这种算法对于单峰函数显然可行(你都知道是单峰函数了为什么不三分呢)。 -但是对于多数需要求解的函数中,爬山算法很容易进入一个局部最优解,如下图(最优解为 $\color{green}{\Uparrow}$,而爬山算法可能找到的最优解为 $\color{red}{\Downarrow}$)。 +但是对于多数需要求解的函数中,爬山算法很容易进入一个局部最优解,如下图(最优解为 $\color{green}{\Uparrow}$ ,而爬山算法可能找到的最优解为 $\color{red}{\Downarrow}$ )。 ![](./images/hill-climbing.png) @@ -18,7 +18,7 @@ ## 代码 -此处代码以 [「BZOJ 3680」吊打 XXX](https://www.lydsy.com/JudgeOnline/problem.php?id=3680)(求 $n$ 个点的带权类费马点)为例。 +此处代码以[「BZOJ 3680」吊打 XXX](https://www.lydsy.com/JudgeOnline/problem.php?id=3680)(求 $n$ 个点的带权类费马点)为例。 ```cpp #include @@ -60,4 +60,4 @@ int main() { ## 劣势 -其实爬山算法的劣势上文已经提及:它容易陷入一个局部最优解。当目标函数不是单峰函数时,这个劣势是致命的。因此我们要引进 [**模拟退火**](/misc/simulated-annealing/)。 +其实爬山算法的劣势上文已经提及:它容易陷入一个局部最优解。当目标函数不是单峰函数时,这个劣势是致命的。因此我们要引进[**模拟退火**](/misc/simulated-annealing/)。 diff --git a/docs/misc/index.md b/docs/misc/index.md index 0f722b0a..d9ce3bc0 100644 --- a/docs/misc/index.md +++ b/docs/misc/index.md @@ -1,13 +1,13 @@ 这个板块主要介绍的是一些难以分类的实用算法、实用技巧 -如果你是一名想学习一些基础技巧的 OIer / 想掌握这个板块 NOIP 需要掌握的知识,请参阅以下类 : +如果你是一名想学习一些基础技巧的 OIer/想掌握这个板块 NOIP 需要掌握的知识,请参阅以下类: - 离线处理 - 各种距离 - 复杂度 - 离散化 -如果你想稍稍进阶,可以参阅下面的类,当然,这些同样对 NOIP 有很大的帮助 : +如果你想稍稍进阶,可以参阅下面的类,当然,这些同样对 NOIP 有很大的帮助: - CDQ 分治 - 莫队 @@ -15,8 +15,8 @@ - 读入输出优化 - 分数规划 -更加进阶或者有兴趣了解难一些的、 NOIP 范围内很难用到的算法,请参阅下面的类 : +更加进阶或者有兴趣了解难一些的、NOIP 范围内很难用到的算法,请参阅下面的类: - 矩阵树定理 - DSU on Tree -- 朱刘算法 ( 并不是最小生成树 ) +- 朱刘算法(并不是最小生成树) diff --git a/docs/misc/io.md b/docs/misc/io.md index a208fe7f..de1a2937 100644 --- a/docs/misc/io.md +++ b/docs/misc/io.md @@ -1,20 +1,20 @@ -在默认情况下,`std::cin(std::cout)` 是极为迟缓的读入(输出)方式,而 `scanf(printf)` 比 `std::cin(std::cout)` 快得多。 +在默认情况下, `std::cin(std::cout)` 是极为迟缓的读入(输出)方式,而 `scanf(printf)` 比 `std::cin(std::cout)` 快得多。 可是为什么会这样呢?有没有什么办法解决读入输出缓慢的问题呢? -## 关闭同步 / 解除绑定 +## 关闭同步/解除绑定 -### `std::ios::sync_with_stdio(false)` +### `std::ios::sync_with_stdio(false)` -这个函数是一个 “是否兼容 stdio” 的开关,C++ 为了兼容 C,保证程序在使用了 `printf` 和 `std::cout` 的时候不发生混乱,将输出流绑到了一起。 +这个函数是一个“是否兼容 stdio”的开关,C++ 为了兼容 C,保证程序在使用了 `printf` 和 `std::cout` 的时候不发生混乱,将输出流绑到了一起。 这其实是 C++ 为了兼容而采取的保守措施。我们可以在 IO 之前将 stdio 解除绑定,这样做了之后要注意不要同时混用 `std::cout` 和 `printf` 之类 -### `tie` +### `tie` tie 是将两个 stream 绑定的函数,空参数的话返回当前的输出流指针。 -在默认的情况下 `std::cin` 绑定的是 `std::cout`,每次执行 `<<` 操作符的时候都要调用 `flush()`,这样会增加 IO 负担。可以通过 `std::cin.tie(0)`(0 表示 NULL)来解除 `std::cin` 与 `std::cout` 的绑定,进一步加快执行效率。 +在默认的情况下 `std::cin` 绑定的是 `std::cout` ,每次执行 `<<` 操作符的时候都要调用 `flush()` ,这样会增加 IO 负担。可以通过 `std::cin.tie(0)` (0 表示 NULL)来解除 `std::cin` 与 `std::cout` 的绑定,进一步加快执行效率。 ### 代码实现 @@ -26,13 +26,13 @@ std::cin.tie(0); ## 读入优化 -`scanf` 和 `printf` 依然有优化的空间,这就是本章所介绍的内容——读入和输出优化。 + `scanf` 和 `printf` 依然有优化的空间,这就是本章所介绍的内容——读入和输出优化。 - 注意,读入和输出优化均针对整数,不支持其他类型的数据 ### 原理 -众所周知,`getchar` 是用来读入 char 类型,且速度很快,用 “读入字符——转换为整形” 来代替缓慢的读入 +众所周知, `getchar` 是用来读入 char 类型,且速度很快,用“读入字符——转换为整形”来代替缓慢的读入 每个整数由两部分组成——符号和数字 @@ -61,15 +61,15 @@ int read() { } ``` -- 举例 +- 举例 -读入 num 可写为 `num=read();` +读入 num 可写为 `num=read();` ## 输出优化 ### 原理 -同样是众所周知,`putchar` 是输出单个字符 +同样是众所周知, `putchar` 是输出单个字符 因此将数字的每一位转化为字符输出以加速 @@ -103,15 +103,15 @@ inline void write(int x) { - 举例 -输出 num 可写为 `write(num);` +输出 num 可写为 `write(num);` -## 更快的读入 / 输出优化 +## 更快的读入/输出优化 -通过 `fread` 或者 `mmap` 可以实现更快的读入。其本质为一次性读入一个巨大的缓存区,如此比一个一个字符读入要快的多 (`getchar`,`putchar`)。 因为硬盘的多次读写速度是要慢于内存的,先一次性读到内存里在读入要快的多。 +通过 `fread` 或者 `mmap` 可以实现更快的读入。其本质为一次性读入一个巨大的缓存区,如此比一个一个字符读入要快的多 ( `getchar` , `putchar` )。因为硬盘的多次读写速度是要慢于内存的,先一次性读到内存里在读入要快的多。 -更通用的是 `fread`,因为 `mmap` 不能在 Windows 使用。 +更通用的是 `fread` ,因为 `mmap` 不能在 Windows 使用。 -`fread` 类似于 `scanf("%s")`,不过它更为快速,而且可以一次性读入若干个字符(包括空格换行等制表符),如果缓存区足够大,甚至可以一次性读入整个文件。 + `fread` 类似于 `scanf("%s")` ,不过它更为快速,而且可以一次性读入若干个字符(包括空格换行等制表符),如果缓存区足够大,甚至可以一次性读入整个文件。 对于输出,我们还有对应的 `fwrite` 函数 @@ -122,7 +122,7 @@ std::size_t fwrite(const void* buffer, std::size_t size, std::size_t count, std::FILE* stream); ``` -使用示例:`fread(Buf, 1, MAXSIZE, stdin)`,如此从 stdin 文件流中读入 MAXSIZE 个大小为 1 的字符到 Buf 中。 +使用示例: `fread(Buf, 1, MAXSIZE, stdin)` ,如此从 stdin 文件流中读入 MAXSIZE 个大小为 1 的字符到 Buf 中。 读入之后的使用就跟普通的读入优化相似了,只需要重定义一下 getchar。它原来是从文件中读入一个 char,现在变成从 Buf 中读入一个 char,也就是头指针向后移动一位。 @@ -134,7 +134,7 @@ char buf[1 << 20], *p1, *p2; : *p1++) ``` -`fwrite` 也是类似的,先放入一个 `OutBuf[MAXSIZE]` 中,最后通过 `fwrite` 一次性将 `OutBuf` 输出。 + `fwrite` 也是类似的,先放入一个 `OutBuf[MAXSIZE]` 中,最后通过 `fwrite` 一次性将 `OutBuf` 输出。 参考代码: diff --git a/docs/misc/magic.md b/docs/misc/magic.md index 0a8be6cb..d2b1c290 100644 --- a/docs/misc/magic.md +++ b/docs/misc/magic.md @@ -2,14 +2,14 @@ 由于众所周知的原因,不是所有境外网站在大陆所有地区都能够正常访问。对于 OIer 而言,进一步学习的需求往往因此难以得到满足。 -注:本项目在编写过程中受到 [@GoogleHosts](https://github.com/GoogleHosts/hosts) 的大力支持,在此表示感谢。 +注:本项目在编写过程中受到[@GoogleHosts](https://github.com/GoogleHosts/hosts)的大力支持,在此表示感谢。 本页仅用于介绍,版权归服务提供者所有。 ## Hosts -[@GoogleHosts](https://github.com/GoogleHosts/hosts) 致力于维护 `hosts` 文件,优化中国大陆对众多网站的访问。`hosts` 文件的作用是将域名指向访问速度更优秀的对应 `ip` 地址,可以避免一定程度的干扰。 +[@GoogleHosts](https://github.com/GoogleHosts/hosts)致力于维护 `hosts` 文件,优化中国大陆对众多网站的访问。 `hosts` 文件的作用是将域名指向访问速度更优秀的对应 `ip` 地址,可以避免一定程度的干扰。 -另外,[@GoogleHosts](https://github.com/GoogleHosts/hosts) 也搭建了 [公益服务](https://github.com/googlehosts/hosts/wiki/%E5%AE%9E%E9%AA%8C%E5%AE%A4),包含 防污染 DNS、Shadowsocks 服务和 Telegram 专用代理等。 +另外,[@GoogleHosts](https://github.com/GoogleHosts/hosts)也搭建了[公益服务](https://github.com/googlehosts/hosts/wiki/%E5%AE%9E%E9%AA%8C%E5%AE%A4),包含 防污染 DNS、Shadowsocks 服务和 Telegram 专用代理等。 ## Shadowsocks @@ -17,23 +17,21 @@ Shadowsocks 是一个安全的 `socks5` 代理,支持多种平台。 ### Windows -下载地址:[shadowsocks-win](https://github.com/shadowsocks/shadowsocks-windows/releases) \| -[Outline Windows](https://raw.githubusercontent.com/Jigsaw-Code/outline-releases/master/client/Outline-Client.exe) +下载地址:[shadowsocks-win](https://github.com/shadowsocks/shadowsocks-windows/releases)\|[Outline Windows](https://raw.githubusercontent.com/Jigsaw-Code/outline-releases/master/client/Outline-Client.exe) ### Mac OS X -下载地址:[ShadowsocksX-NG](https://github.com/shadowsocks/ShadowsocksX-NG/releases) \| -[Outline macOS](https://itunes.apple.com/app/outline-app/id1356178125) +下载地址:[ShadowsocksX-NG](https://github.com/shadowsocks/ShadowsocksX-NG/releases)\|[Outline macOS](https://itunes.apple.com/app/outline-app/id1356178125) ### Linux 自动安装脚本:[lrinQVQ/script](https://github.com/lrinQVQ/script) -### 为什么不推荐使用 ssr ? +### 为什么不推荐使用 ssr? -ssr 在开发过程中违反了 [GPL 协议](https://zh.wikipedia.org/wiki/GNU%E9%80%9A%E7%94%A8%E5%85%AC%E5%85%B1%E8%AE%B8%E5%8F%AF%E8%AF%81),GPL 要求再分发时应开放源代码,而 ssr 的维护者并没有做到这一点。作为一个合格的 OIer, **遵守开源协议** 是最起码的要求,所以这里并不提倡使用 ssr。 +ssr 在开发过程中违反了[GPL 协议](https://zh.wikipedia.org/wiki/GNU%E9%80%9A%E7%94%A8%E5%85%AC%E5%85%B1%E8%AE%B8%E5%8F%AF%E8%AF%81),GPL 要求再分发时应开放源代码,而 ssr 的维护者并没有做到这一点。作为一个合格的 OIer,**遵守开源协议**是最起码的要求,所以这里并不提倡使用 ssr。 -引用 clowwindy 的一段著名的 [评论](https://github.com/shadowsocks/shadowsocks-windows/issues/293#issuecomment-132253168) 作为结语。 +引用 clowwindy 的一段著名的[评论](https://github.com/shadowsocks/shadowsocks-windows/issues/293#issuecomment-132253168)作为结语。 ## 关于中国的互联网 @@ -45,7 +43,7 @@ ssr 在开发过程中违反了 [GPL 协议](https://zh.wikipedia.org/wiki/GNU%E 我们希望您能在遇到此类言论和见解时,不要不加思考地、情绪一度被煽动而不能克制地、盲目地相信这些片面或者歪曲事实的东西,而是要事实求是地思考,要摆脱情绪绑架的怪诞思维去理解。 -我们需要了解到,中国的发展总基调是 “稳中求进”,中国社会的最核心问题就是稳定。失去稳定的中国将会是一盘散沙,面临分裂和肢解的危险。我们希望您了解当今中国发展的根本保证是什么,发展的过程中哪部分是主旋律,哪些是噪音;哪些是进取的,哪些是会开历史倒车的。我们需要维新,而不是革命。 +我们需要了解到,中国的发展总基调是“稳中求进”,中国社会的最核心问题就是稳定。失去稳定的中国将会是一盘散沙,面临分裂和肢解的危险。我们希望您了解当今中国发展的根本保证是什么,发展的过程中哪部分是主旋律,哪些是噪音;哪些是进取的,哪些是会开历史倒车的。我们需要维新,而不是革命。 我们应该清醒和全面地认识问题,偏信西方媒体的言论、缺乏对国家的信任是不可取的。您的数据安全和隐私对您尤为重要。若这些信息不应当被西方掌握时,它对国家和民族的命运更为重要。出于此原因,当局可能会限制您的行为,我们不会也无法干预当局的任何政策和决定。 diff --git a/docs/misc/matrix-tree.md b/docs/misc/matrix-tree.md index 7ae62f0a..c135ff24 100644 --- a/docs/misc/matrix-tree.md +++ b/docs/misc/matrix-tree.md @@ -18,13 +18,13 @@ $$ A_{ij}(G)=\#e(i,j), i\neq j $$ -定义 Laplace 矩阵(亦称 Kirchhoff 矩阵)$L$为: +定义 Laplace 矩阵(亦称 Kirchhoff 矩阵) $L$ 为: $$ L(G) = D(G) - A(G) $$ -记图 $G$ 的所有生成树个数为 $t(G)$。 +记图 $G$ 的所有生成树个数为 $t(G)$ 。 ### 有向图情况 @@ -34,7 +34,7 @@ $$ D^{out}_{ii}(G) = \mathrm{deg^{out}}(i), D^{out}_{ij} = 0, i\neq j $$ -类似地定理入度矩阵 $D^{in}(G)$ +类似地定理入度矩阵 $D^{in}(G)$ 设 $\#e(i,j)$ 为点 $i$ 指向点 $j$ 的有向边数,并定义邻接矩阵 $A$ 为: @@ -48,17 +48,17 @@ $$ L^{out}(G) = D^{out}(G) - A(G) $$ -类似地定义入度 Laplace 矩阵 $L^{in}$。 +类似地定义入度 Laplace 矩阵 $L^{in}$ 。 -记图 $G$ 的以 $r$ 为根的所有根向树形图个数为 $t^{root}(G,r)$。所谓根向树形图,是说这张图的基图是一棵树,所有的边全部指向父亲。 +记图 $G$ 的以 $r$ 为根的所有根向树形图个数为 $t^{root}(G,r)$ 。所谓根向树形图,是说这张图的基图是一棵树,所有的边全部指向父亲。 -记图 $G$ 的以 $r$ 为根的所有叶向树形图个数为 $t^{leaf}(G,r)$。所谓叶向树形图,是说这张图的基图是一棵树,所有的边全部指向儿子。 +记图 $G$ 的以 $r$ 为根的所有叶向树形图个数为 $t^{leaf}(G,r)$ 。所谓叶向树形图,是说这张图的基图是一棵树,所有的边全部指向儿子。 ## 定理叙述 矩阵树定理具有多种形式。其中用得较多的是定理 1、定理 3 与定理 4。 -**定理 1 (矩阵树定理,无向图行列式形式)** 对于任意的 $i$,都有 +**定理 1(矩阵树定理,无向图行列式形式)**对于任意的 $i$ ,都有 $$ t(G) = \det L(G)\binom{1,2,\cdots,i-1,i+1,\cdots,n}{1,2,\cdots,i-1,i+1,\cdots,n} @@ -66,11 +66,11 @@ $$ 其中记号 $L(G)\binom{1,2,\cdots,i-1,i+1,\cdots,n}{1,2,\cdots,i-1,i+1,\cdots,n}$ 表示矩阵 $L(G)$ 的第 $1,\cdots,i-1,i+1,\cdots,n$ 行与第 $1,\cdots,i-1,i+1,\cdots,n$ 列构成的子矩阵。也就是说,无向图的 Laplace 矩阵具有这样的性质,它的所有 $n-1$ 阶主子式都相等。 -**定理 2 (矩阵树定理,无向图特征值形式)** 设 $\lambda_1, \lambda_2, \cdots, \lambda_{n-1}$ 为 $L(G)$ 的 $n - 1$ 个非零特征值,那么有 +**定理 2(矩阵树定理,无向图特征值形式)**设 $\lambda_1, \lambda_2, \cdots, \lambda_{n-1}$ 为 $L(G)$ 的 $n - 1$ 个非零特征值,那么有 -$t(G) = \frac{1}{n}\lambda_1\lambda_2\cdots\lambda_{n-1}$ + $t(G) = \frac{1}{n}\lambda_1\lambda_2\cdots\lambda_{n-1}$ -**定理 3 (矩阵树定理,有向图根向形式)** 对于任意的 $k$,都有 +**定理 3(矩阵树定理,有向图根向形式)**对于任意的 $k$ ,都有 $$ t^{root}(G,k) = \det L^{out}(G)\binom{1,2,\cdots,k-1,k+1,\cdots,n}{1,2,\cdots,k-1,k+1,\cdots,n} @@ -78,7 +78,7 @@ $$ 因此如果要统计一张图所有的根向树形图,只要枚举所有的根 $k$ 并对 $t^{root}(G,k)$ 求和即可。 -**定理 4 (矩阵树定理,有向图叶向形式)** 对于任意的 $k$,都有 +**定理 4(矩阵树定理,有向图叶向形式)**对于任意的 $k$ ,都有 $$ t^{leaf}(G,k) = \det L^{in}(G)\binom{1,2,\cdots,k-1,k+1,\cdots,n}{1,2,\cdots,k-1,k+1,\cdots,n} @@ -88,25 +88,25 @@ $$ ## BEST 定理 -**定理 5 (BEST 定理)** 设$G$是有向欧拉图,那么$G$的不同欧拉回路总数$ec(G)$是 +**定理 5 (BEST 定理)**设 $G$ 是有向欧拉图,那么 $G$ 的不同欧拉回路总数 $ec(G)$ 是 $$ ec(G) = t^{root}(G,k)\prod_{v\in V}(\deg (v) - 1)! $$ -注意,对欧拉图$G$的任意两个节点$k, k'$,都有$t^{root}(G,k)=t^{root}(G,k')$,且欧拉图$G$的所有节点的入度和出度相等。 +注意,对欧拉图 $G$ 的任意两个节点 $k, k'$ ,都有 $t^{root}(G,k)=t^{root}(G,k')$ ,且欧拉图 $G$ 的所有节点的入度和出度相等。 ## 例题 ???+ note "例题 1" HEOI2015: 小 Z 的房间,请参考 -**解** 矩阵树定理的裸题。将每个空房间看作一个结点,根据输入的信息建图,得到 Laplace 矩阵后,任意删掉 L 的第 $i$ 行第 $i$ 列,求这个子式的行列式即可。求行列式的方法就是高斯消元成上三角阵然后算对角线积。另外本题需要在模 $k$ 的整数子环 $\mathbb{Z}_k$ 上进行高斯消元,采用辗转相除法即可。 +**解**矩阵树定理的裸题。将每个空房间看作一个结点,根据输入的信息建图,得到 Laplace 矩阵后,任意删掉 L 的第 $i$ 行第 $i$ 列,求这个子式的行列式即可。求行列式的方法就是高斯消元成上三角阵然后算对角线积。另外本题需要在模 $k$ 的整数子环 $\mathbb{Z}_k$ 上进行高斯消元,采用辗转相除法即可。 ???+ note "例题 2" FJOI2007: 轮状病毒。请参考 -**解** 本题的解法很多,这里用矩阵树定理是最直接的解法。当输入为 $n$ 时,容易写出其 $n+1$ 阶的 Laplace 矩阵为: +**解**本题的解法很多,这里用矩阵树定理是最直接的解法。当输入为 $n$ 时,容易写出其 $n+1$ 阶的 Laplace 矩阵为: $$ L_n = \begin{bmatrix} @@ -123,9 +123,9 @@ $$ 求出它的 $n$ 阶子式的行列式即可,剩下的只有高精度计算了。 ???+ note "例题 2+" - 将例题 2 的数据加强,要求 $n\leq 100000$,但是答案对 1000007 取模。(本题求解需要一些线性代数知识) + 将例题 2 的数据加强,要求 $n\leq 100000$ ,但是答案对 1000007 取模。(本题求解需要一些线性代数知识) -**解** 推导递推式后利用矩阵快速幂即可求得。 +**解**推导递推式后利用矩阵快速幂即可求得。 ??? danger "推导递推式的过程。警告:过程冗杂" @@ -181,7 +181,7 @@ $$ ???+ note "例题 3" BZOJ3659: WHICH DREAMED IT -**解** 本题是 BEST 定理的直接应用,但是要注意,由于题目规定 “两种完成任务的方式算作不同当且仅当使用钥匙的顺序不同”,对每个欧拉回路,1 号房间可以沿着任意一条出边出发,从而答案还要乘以 1 号房间的出度。 +**解**本题是 BEST 定理的直接应用,但是要注意,由于题目规定“两种完成任务的方式算作不同当且仅当使用钥匙的顺序不同”,对每个欧拉回路,1 号房间可以沿着任意一条出边出发,从而答案还要乘以 1 号房间的出度。 ## 注释 diff --git a/docs/misc/mo-algo.md b/docs/misc/mo-algo.md index bcdea9c2..3557ebf4 100644 --- a/docs/misc/mo-algo.md +++ b/docs/misc/mo-algo.md @@ -1,6 +1,6 @@ ## 普通莫队算法 -(主要参考了 。) +(主要参考了。) ### 概述 @@ -8,7 +8,7 @@ ### 形式 -假设 $n=m$,那么对于序列上的区间询问问题,如果从 $[l,r]$ 的答案能够 $O(1)$ 扩展到 $[l-1,r],[l+1,r],[l,r+1],[l,r-1]$(即与 $[l,r]$ 相邻的区间)的答案,那么可以在 $O(n\sqrt{n})$ 的复杂度内求出所有询问的答案。 +假设 $n=m$ ,那么对于序列上的区间询问问题,如果从 $[l,r]$ 的答案能够 $O(1)$ 扩展到 $[l-1,r],[l+1,r],[l,r+1],[l,r-1]$ (即与 $[l,r]$ 相邻的区间)的答案,那么可以在 $O(n\sqrt{n})$ 的复杂度内求出所有询问的答案。 ### 实现 @@ -16,7 +16,7 @@ ### 排序方法 -对于区间 $[l,r]$, 以 $l$ 所在块的编号为第一关键字,$r$ 为第二关键字从小到大排序。 +对于区间 $[l,r]$ , 以 $l$ 所在块的编号为第一关键字, $r$ 为第二关键字从小到大排序。 ### 模板 @@ -43,21 +43,21 @@ void solve() { 以下的情况在 $n$ 和 $m$ 同阶的前提下讨论。 -首先是分块这一步,这一步的时间复杂度毫无疑问地是 $O(\sqrt{n}\cdot\sqrt{n}\log\sqrt{n}+n\log n)=O(n\log n)$; +首先是分块这一步,这一步的时间复杂度毫无疑问地是 $O(\sqrt{n}\cdot\sqrt{n}\log\sqrt{n}+n\log n)=O(n\log n)$ ; -接着就到了莫队算法的精髓了,下面我们用通俗易懂的初中方法来证明它的时间复杂度是 $O(n\sqrt{n})$; +接着就到了莫队算法的精髓了,下面我们用通俗易懂的初中方法来证明它的时间复杂度是 $O(n\sqrt{n})$ ; -证:令每一块中 $L$ 的最大值为 $\max_1,\max_2,\max_3, \cdots , \max_{\lceil\sqrt{n}\rceil}$。 +证:令每一块中 $L$ 的最大值为 $\max_1,\max_2,\max_3, \cdots , \max_{\lceil\sqrt{n}\rceil}$ 。 -由第一次排序可知,$\max_1 \le \max_2 \le \cdots \le \max_{\lceil\sqrt{n}\rceil}$。 +由第一次排序可知, $\max_1 \le \max_2 \le \cdots \le \max_{\lceil\sqrt{n}\rceil}$ 。 -显然,对于每一块暴力求出第一个询问的时间复杂度为 $O(n)$。 +显然,对于每一块暴力求出第一个询问的时间复杂度为 $O(n)$ 。 -考虑最坏的情况,在每一块中,$R$ 的最大值均为 $n$,每次修改操作均要将 $L$ 由 $\max_{i - 1}$ 修改至 $\max_i$ 或由 $\max_i$ 修改至 $\max_{i - 1}$。 +考虑最坏的情况,在每一块中, $R$ 的最大值均为 $n$ ,每次修改操作均要将 $L$ 由 $\max_{i - 1}$ 修改至 $\max_i$ 或由 $\max_i$ 修改至 $\max_{i - 1}$ 。 -考虑 $R$:因为 $R$ 在块中已经排好序,所以在同一块修改完它的时间复杂度为 $O(n)$。对于所有块就是 $O(n\sqrt{n})$。 +考虑 $R$ :因为 $R$ 在块中已经排好序,所以在同一块修改完它的时间复杂度为 $O(n)$ 。对于所有块就是 $O(n\sqrt{n})$ 。 -重点分析 $L$:因为每一次改变的时间复杂度都是 $O(\max_i-\max_{i-1})$ 的,所以在同一块中时间复杂度为 $O(\sqrt{n}\cdot(\max_i-\max_{i-1}))$。 +重点分析 $L$ :因为每一次改变的时间复杂度都是 $O(\max_i-\max_{i-1})$ 的,所以在同一块中时间复杂度为 $O(\sqrt{n}\cdot(\max_i-\max_{i-1}))$ 。 将每一块 $L$ 的时间复杂度合在一起,可以得到: @@ -71,17 +71,17 @@ $$ \end{aligned} $$ -(裂项求和) +(裂项求和) -由题可知 $\max_{\lceil\sqrt{n}\rceil}$ 最大为 $n$,所以 $L$ 的总时间复杂度最坏情况下为 $O(n\sqrt{n})$。 +由题可知 $\max_{\lceil\sqrt{n}\rceil}$ 最大为 $n$ ,所以 $L$ 的总时间复杂度最坏情况下为 $O(n\sqrt{n})$ 。 -综上所述,莫队算法的时间复杂度为 $O(n\sqrt{n})$; +综上所述,莫队算法的时间复杂度为 $O(n\sqrt{n})$ ; -但是对于 $m$ 的其他取值,如 $m>1`。 +还有,算 $C_a^2$ 可以用位运算, `a/2` 可以写成 `a>>1` 。 -算法总复杂度:$O(n\sqrt{n} )$ +算法总复杂度: $O(n\sqrt{n} )$ -下面的代码中 `mot` 表示答案的分母 (mother),`sub` 表示分子,`sqn` 表示块的大小:$\sqrt{n}$,`arr` 是输入的数组,`node` 是存储询问的结构体,`tab` 是询问序列(排序后的),`col` 同上所述。 - 注意:下面代码中的移动区间的 4 个 for 循环的位置很关键,不能改变它们之间的位置关系,不然会 WA(因为有那个 `++l` 和 `--r`)。 -代码: +下面的代码中 `mot` 表示答案的分母 (mother), `sub` 表示分子, `sqn` 表示块的大小: $\sqrt{n}$ , `arr` 是输入的数组, `node` 是存储询问的结构体, `tab` 是询问序列(排序后的), `col` 同上所述。注意:下面代码中的移动区间的 4 个 for 循环的位置很关键,不能改变它们之间的位置关系,不然会 WA(因为有那个 `++l` 和 `--r` )。代码: ```cpp #include @@ -173,7 +171,7 @@ int main() { 4 100 ``` -手动模拟一下可以发现,r 指针的移动次数大概为 300 次,我们处理完第一个块之后,$l = 2, r = 100$,此时只需要移动两次 l 指针就可以得到第四个询问的答案,但是我们却将 r 指针移动到 1 来获取第三个询问的答案,再移动到 100 获取第四个询问的答案,这样多了九十几次的指针移动。我们怎么优化这个地方呢?这里我们就要用到奇偶化排序 +手动模拟一下可以发现,r 指针的移动次数大概为 300 次,我们处理完第一个块之后, $l = 2, r = 100$ ,此时只需要移动两次 l 指针就可以得到第四个询问的答案,但是我们却将 r 指针移动到 1 来获取第三个询问的答案,再移动到 100 获取第四个询问的答案,这样多了九十几次的指针移动。我们怎么优化这个地方呢?这里我们就要用到奇偶化排序 什么是奇偶化排序?奇偶化排序即对于属于奇数块的询问,r 按从小到大排序,对于属于偶数块的排序,r 从大到小排序,这样我们的 r 指针在处理完这个奇数块的问题后,将在返回的途中处理偶数块的问题,再向 n 移动处理下一个奇数块的问题,优化了 r 指针的移动次数,一般情况下,这种优化能让程序快 30% 左右 @@ -217,38 +215,38 @@ struct node { ## 带修改 请确保您已经会普通莫队算法了。 -如果您还不会,请先阅读上面的 “普通莫队算法” +如果您还不会,请先阅读上面的“普通莫队算法” ### 特点 普通莫队是不能带修改的 -我们可以强行让它可以修改,就像 DP 一样,可以强行加上一维 ** 时间维 **, 表示这次操作的时间。 +我们可以强行让它可以修改,就像 DP 一样,可以强行加上一维**时间维**, 表示这次操作的时间。 时间维表示经历的修改次数。 -即把询问 $[l,r]$ 变成 $[l,r,time]$ +即把询问 $[l,r]$ 变成 $[l,r,time]$ 那么我们的坐标也可以在时间维上移动,即 $[l,r,time]$ 多了一维可以移动的方向,可以变成: -- $[l-1,r,time]$ -- $[l+1,r,time]$ -- $[l,r-1,time]$ -- $[l,r+1,time]$ -- $[l,r,time-1]$ -- $[l,r,time+1]$ +- $[l-1,r,time]$ +- $[l+1,r,time]$ +- $[l,r-1,time]$ +- $[l,r+1,time]$ +- $[l,r,time-1]$ +- $[l,r,time+1]$ 这样的转移也是 $O(1)$ 的,但是我们排序又多了一个关键字,再搞搞就行了 -可以用和普通莫队类似的方法排序转移,做到 $O(n^{\frac{5}{3}})$ +可以用和普通莫队类似的方法排序转移,做到 $O(n^{\frac{5}{3}})$ 这一次我们排序的方式是以 $n^{\frac{2}{3}}$ 为一块,分成了 $n^{\frac{1}{3}}$ 块,第一关键字是左端点所在块,第二关键字是右端点所在块,第三关键字是时间。 -还是来证明一下时间复杂度(默认块大小为 $\sqrt{n}$): +还是来证明一下时间复杂度(默认块大小为 $\sqrt{n}$ ): -- 左右端点所在块不变,时间在排序后单调向右移,这样的复杂度是 $O(n)$ -- 若左右端点所在块改变,时间一次最多会移动 n 个格子,时间复杂度 $O(n)$ -- 左端点所在块一共有 $n^{\frac{1}{3}}$ 中,右端点也是 $n^{\frac{1}{3}}$ 种,一共 ${n^{\frac{1}{3}}}\times{n^{\frac{1}{3}}}=n^{\frac{2}{3}}$ 种,每种乘上移动的复杂度 $O(n)$,总复杂度 $O(n^{\frac{5}{3}})$ +- 左右端点所在块不变,时间在排序后单调向右移,这样的复杂度是 $O(n)$ +- 若左右端点所在块改变,时间一次最多会移动 n 个格子,时间复杂度 $O(n)$ +- 左端点所在块一共有 $n^{\frac{1}{3}}$ 中,右端点也是 $n^{\frac{1}{3}}$ 种,一共 ${n^{\frac{1}{3}}}\times{n^{\frac{1}{3}}}=n^{\frac{2}{3}}$ 种,每种乘上移动的复杂度 $O(n)$ ,总复杂度 $O(n^{\frac{5}{3}})$ ### 例题 @@ -261,21 +259,21 @@ struct node { 我们不难发现,如果不带操作 1(修改)的话,我们就能轻松用普通莫队解决。 -但是题目还带单点修改,所以用 ** 带修改的莫队 **。 +但是题目还带单点修改,所以用**带修改的莫队**。 先考虑普通莫队的做法: -- 每次扩大区间时,每加入一个数字,则统计它已经出现的次数,如果加入前这种数字出现次数为 $0$,则说明这是一种新的数字,答案 $+1$。然后这种数字的出现次数 $+1$。 -- 每次减小区间时,每删除一个数字,则统计它删除后的出现次数,如果删除后这种数字出现次数为 $0$,则说明这种数字已经从当前的区间内删光了,也就是当前区间减少了一种颜色,答案 $-1$。然后这种数字的出现次数 $-1$。 +- 每次扩大区间时,每加入一个数字,则统计它已经出现的次数,如果加入前这种数字出现次数为 $0$ ,则说明这是一种新的数字,答案 $+1$ 。然后这种数字的出现次数 $+1$ 。 +- 每次减小区间时,每删除一个数字,则统计它删除后的出现次数,如果删除后这种数字出现次数为 $0$ ,则说明这种数字已经从当前的区间内删光了,也就是当前区间减少了一种颜色,答案 $-1$ 。然后这种数字的出现次数 $-1$ 。 现在再来考虑修改: - 单点修改,把某一位的数字修改掉。假如我们是从一个经历修改次数为 $i$ 的询问转移到一个经历修改次数为 $j$ 的询问上,且 $i **什么是退火?**(选自百度百科) > @@ -18,7 +18,7 @@ 先用一句话概括:如果新状态的解更优则修改答案,否则以一定概率接受新状态。 -我们定义当前温度为 $T$,新状态与已知状态(由已知状态通过随机的方式得到)之间的能量(值)差为 $\Delta E$($\Delta E\geqslant 0$),则发生状态转移(修改最优解)的概率为 +我们定义当前温度为 $T$ ,新状态与已知状态(由已知状态通过随机的方式得到)之间的能量(值)差为 $\Delta E$ ( $\Delta E\geqslant 0$ ),则发生状态转移(修改最优解)的概率为 $$ P(\Delta E)= @@ -32,11 +32,11 @@ $$ ### 如何退火(降温)? -模拟退火时我们有三个参数:初始温度 $T_0$,降温系数 $d$,终止温度 $T_k$。其中 $T_0$ 是一个比较大的数,$d$ 是一个非常接近 $1$ 但是小于 $1$ 的数,$T_k$ 是一个接近 $0$ 的正数。 +模拟退火时我们有三个参数:初始温度 $T_0$ ,降温系数 $d$ ,终止温度 $T_k$ 。其中 $T_0$ 是一个比较大的数, $d$ 是一个非常接近 $1$ 但是小于 $1$ 的数, $T_k$ 是一个接近 $0$ 的正数。 -首先让温度 $T=T_0$,然后按照上述步骤进行一次转移尝试,再让 $T=d\cdot T$。当 $T diff --git a/docs/search/astar.md b/docs/search/astar.md index 79e5df0a..4982acf4 100644 --- a/docs/search/astar.md +++ b/docs/search/astar.md @@ -1,24 +1,24 @@ -A\* 算法是 [BFS](/search/bfs) 的一种改进。 +A\*算法是[BFS](/search/bfs)的一种改进。 -定义起点 $s$,终点 $t$。 +定义起点 $s$ ,终点 $t$ 。 -从起点(初始状态)开始的距离函数 $g(x)$。 +从起点(初始状态)开始的距离函数 $g(x)$ 。 -到终点(最终状态)的距离函数 $h(x), h*(x)$。 +到终点(最终状态)的距离函数 $h(x), h*(x)$ 。 -定义每个点的估价函数 $f(x)=g(x)+h(x)$。 +定义每个点的估价函数 $f(x)=g(x)+h(x)$ 。 -A\* 算法每次从 **优先队列** 中取出一个 $f$ 最小的,然后更新相邻的状态。 +A\*算法每次从**优先队列**中取出一个 $f$ 最小的,然后更新相邻的状态。 -如果 $h\leq h*$,则 A\* 算法能找到最优解。 +如果 $h\leq h*$ ,则 A\*算法能找到最优解。 -上述条件下,如果 $h$ **满足三角形不等式,则 A\* 算法不会将重复结点加入队列**。 +上述条件下,如果 $h$ **满足三角形不等式,则 A\*算法不会将重复结点加入队列**。 -其实…… $h=0$ 时就是 [DFS](/search/dfs) 算法,$h=0$ 并且边权为 $1$ 时就是 [BFS](/search/BFS)。 +其实…… $h=0$ 时就是[DFS](/search/dfs)算法, $h=0$ 并且边权为 $1$ 时就是[BFS](/search/BFS)。 -## 例题 [八数码](https://www.luogu.org/problemnew/show/P1379) +## 例题[八数码](https://www.luogu.org/problemnew/show/P1379) -题目大意:在 $3\times 3$ 的棋盘上,摆有八个棋子,每个棋子上标有 1 至 8 的某一数字。棋盘中留有一个空格,空格用 0 来表示。空格周围的棋子可以移到空格中,这样原来的位置就会变成空格。给出一种初始布局和目标布局(为了使题目简单, 设目标状态为 +题目大意:在 $3\times 3$ 的棋盘上,摆有八个棋子,每个棋子上标有 1 至 8 的某一数字。棋盘中留有一个空格,空格用 0 来表示。空格周围的棋子可以移到空格中,这样原来的位置就会变成空格。给出一种初始布局和目标布局(为了使题目简单,设目标状态为 123 804 @@ -26,9 +26,9 @@ A\* 算法每次从 **优先队列** 中取出一个 $f$ 最小的,然后更 ),找到一种从初始布局到目标布局最少步骤的移动方法。 -$h$ 函数可以定义为,不在应该在的位置的数字个数。 + $h$ 函数可以定义为,不在应该在的位置的数字个数。 -容易发现 $h$ 满足以上两个性质,此题可以使用 A\* 算法求解。 +容易发现 $h$ 满足以上两个性质,此题可以使用 A\*算法求解。 代码实现: @@ -104,17 +104,17 @@ int main() { } ``` -## 例题 [k 短路](https://www.luogu.org/problemnew/show/P2483) +## 例题[k 短路](https://www.luogu.org/problemnew/show/P2483) 题目大意:按顺序求一个有向图上从结点 $s$ 到结点 $t$ 的所有路径最小的前任意多(不妨设为 $k$ )个。 -很容易发现,这个问题很容易转化成用 A\* 算法解决问题的标准程式。 +很容易发现,这个问题很容易转化成用 A\*算法解决问题的标准程式。 -初始状态为处于结点 $s$,最终状态为处于结点 $t$,距离函数为从 $s$ 到当前结点已经走过的距离,估价函数为从当前结点到结点 $t$ 至少要走过的距离,也就是当前结点到结点 $t$ 的最短路。 +初始状态为处于结点 $s$ ,最终状态为处于结点 $t$ ,距离函数为从 $s$ 到当前结点已经走过的距离,估价函数为从当前结点到结点 $t$ 至少要走过的距离,也就是当前结点到结点 $t$ 的最短路。 就这样,我们在预处理的时候反向建图,计算出结点 $t$ 到所有点的最短路,然后将初始状态塞入优先队列,每次取出 $f(x)=g(x)+h(x)$ 最小的一项,计算出其所连结点的信息并将其也塞入队列。当你第 $k$ 次走到结点 $t$ 时,也就算出了结点 $s$ 到结点 $t$ 的 $k$ 短路。 -由于设计的距离函数和估价函数,每个状态需要存储两个参数,当前结点 $x$ 和已经走过的距离 $v$。 +由于设计的距离函数和估价函数,每个状态需要存储两个参数,当前结点 $x$ 和已经走过的距离 $v$ 。 我们可以在此基础上加一点小优化:由于只需要求出第 $k$ 短路,所以当我们第 $k+1$ 次或以上走到该结点时,直接跳过该状态。因为前面的 $k$ 次走到这个点的时候肯定能因此构造出 $k$ 条路径,所以之后在加边更无必要。 diff --git a/docs/search/bfs.md b/docs/search/bfs.md index 45aacf8c..39f5b50e 100644 --- a/docs/search/bfs.md +++ b/docs/search/bfs.md @@ -1,11 +1,11 @@ -BFS 全称是 [Breadth First Search](https://en.wikipedia.org/wiki/Breadth-first_search),中文名是宽度优先搜索,也叫广度优先搜索。 +BFS 全称是[Breadth First Search](https://en.wikipedia.org/wiki/Breadth-first_search),中文名是宽度优先搜索,也叫广度优先搜索。 是图上最基础、最重要的搜索算法之一。 所谓宽度优先。就是每次都尝试访问同一层的节点。 如果同一层都访问完了,再访问下一层。 -这样做的结果是,BFS 算法找到的路径是从起点开始的 ** 最短 ** 合法路径。换言之,这条路所包含的边数最小。 +这样做的结果是,BFS 算法找到的路径是从起点开始的**最短**合法路径。换言之,这条路所包含的边数最小。 在 BFS 结束时,每个节点都是通过从起点到该点的最短路径访问的。 @@ -78,7 +78,7 @@ void restore(int x) { 有了 p 数组,可以方便地还原出起点到一个点的最短路径。上面的 restore 函数就是在做这件事:restore(x) 输出的是从起点到 x 这个点所经过的点。 -时间复杂度 $O(n + m)$ +时间复杂度 $O(n + m)$ 空间复杂度 $O(n)$ (vis 数组和队列) @@ -86,22 +86,22 @@ void restore(int x) { 在实现 BFS 的时候,我们把未被访问过的节点放在一个称为 open 的容器中,而把已经访问过了的节点放在 closed 容器中。 -## [在树 / 图上 BFS](/graph/traverse) +## [在树/图上 BFS](/graph/traverse) ## 应用 - 在一个无权图上求从起点到其他所有点的最短路径。 - 在 $O(n+m)$ 时间内求出所有连通块。(我们只需要从每个没有被访问过的节点开始做 BFS,显然每次 BFS 会走完一个连通块) - 如果把一个游戏的动作看做是状态图上的一条边(一个转移),那么 BFS 可以用来找到在游戏中从一个状态到达另一个状态所需要的最小步骤。 -- 在一个边权为 0 / 1 的图上求最短路。(需要修改入队的过程,如果某条边权值为 0,且可以减小边的终点到图的起点的距离,那么把边的起点加到队列首而不是队列尾) +- 在一个边权为 0/1 的图上求最短路。(需要修改入队的过程,如果某条边权值为 0,且可以减小边的终点到图的起点的距离,那么把边的起点加到队列首而不是队列尾) - 在一个有向无权图中找最小环。(从每个点开始 BFS,在我们即将抵达一个之前访问过的点开始的时候,就知道遇到了一个环。图的最小环是每次 BFS 得到的最小环的平均值。) -- 找到一定在 $(a, b)$ 最短路上的边。(分别从 a 和 b 进行 BFS,得到两个 d 数组。之后对每一条边 $(u, v)$,如果 $d_a[u]+1+d_b[v]=d_a[b]$,则说明该边在最短路上) -- 找到一定在 $(a, b)$ 最短路上的点。(分别从 a 和 b 进行 BFS,得到两个 d 数组。之后对每一个点 v,如果 $d_a[u]+d_b[v]=d_a[b]$,则说明该点在最短路上) -- 找到一条长度为偶数的最短路。(我们需要一个构造一个新图,把每个点拆成两个新点,原图的边 $(u, v)$ 变成 $((u, 0), (v, 1))$ 和 $((u, 1), (v, 0))$。对新图做 BFS,$(s, 0)$ 和 $(t, 0)$ 之间的最短路即为所求) +- 找到一定在 $(a, b)$ 最短路上的边。(分别从 a 和 b 进行 BFS,得到两个 d 数组。之后对每一条边 $(u, v)$ ,如果 $d_a[u]+1+d_b[v]=d_a[b]$ ,则说明该边在最短路上) +- 找到一定在 $(a, b)$ 最短路上的点。(分别从 a 和 b 进行 BFS,得到两个 d 数组。之后对每一个点 v,如果 $d_a[u]+d_b[v]=d_a[b]$ ,则说明该点在最短路上) +- 找到一条长度为偶数的最短路。(我们需要一个构造一个新图,把每个点拆成两个新点,原图的边 $(u, v)$ 变成 $((u, 0), (v, 1))$ 和 $((u, 1), (v, 0))$ 。对新图做 BFS, $(s, 0)$ 和 $(t, 0)$ 之间的最短路即为所求) ## 例题 -- [LOJ#2317. 「NOIP2017」奶酪](https://loj.ac/problem/2317) +- [LOJ#2317.「NOIP2017」奶酪](https://loj.ac/problem/2317) ## 参考 @@ -212,12 +212,12 @@ int main() { ## 优先队列 BFS -优先队列,相当于一个二叉堆,STL 中提供了 [`std::priority_queue`](/ds/stl/priority_queue/),可以方便我们使用优先队列。 +优先队列,相当于一个二叉堆,STL 中提供了[ `std::priority_queue` ](/ds/stl/priority_queue/),可以方便我们使用优先队列。 在基于优先队列的 BFS 中,我们每次从队首取出代价最小的结点进行进一步搜索。容易证明这个贪心思想是正确的,因为从这个结点开始扩展的搜索,一定不会更新原来那些代价更高的结点。换句话说,其余那些代价更高的结点,我们不回去考虑更新它。 当然,每个结点可能会被入队多次,只是每次入队的代价不同。当该结点第一次从优先队列中取出,以后便无需再在该结点进行搜索,直接忽略即可。所以,优先队列的 BFS 当中,每个结点只会被处理一次。 -相对于普通队列的 BFS,时间复杂度多了一个 $\log$,毕竟要维护这个优先队列嘛。不过普通 BFS 有可能每个结点入队、出队多次,时间复杂度会达到 $O(n^2)$,不是 $O(n)$。所以优先队列 BFS 通常还是快的。 +相对于普通队列的 BFS,时间复杂度多了一个 $\log$ ,毕竟要维护这个优先队列嘛。不过普通 BFS 有可能每个结点入队、出队多次,时间复杂度会达到 $O(n^2)$ ,不是 $O(n)$ 。所以优先队列 BFS 通常还是快的。 -诶?这怎么听起来这么像堆优化的 [Dijkstra](/graph/shortest-path/#dijkstra) 算法呢?事实上,堆优化 Dijkstra 就是优先队列 BFS。 +诶?这怎么听起来这么像堆优化的[Dijkstra](/graph/shortest-path/#dijkstra)算法呢?事实上,堆优化 Dijkstra 就是优先队列 BFS。 diff --git a/docs/search/dfs.md b/docs/search/dfs.md index 0d875cfd..16027f9b 100644 --- a/docs/search/dfs.md +++ b/docs/search/dfs.md @@ -1,4 +1,4 @@ -DFS 全称是 [Depth First Search](https://en.wikipedia.org/wiki/Depth-first_search)。 +DFS 全称是[Depth First Search](https://en.wikipedia.org/wiki/Depth-first_search)。 是一种图的遍历算法。 @@ -79,10 +79,10 @@ void dfs(int u) { } ``` -时间复杂度 $O(n + m)$。 +时间复杂度 $O(n + m)$ 。 -空间复杂度 $O(n)$。 (vis 数组和递归栈) +空间复杂度 $O(n)$ 。(vis 数组和递归栈) -## 在树 / 图上 DFS +## 在树/图上 DFS -主条目:[在树 / 图上 DFS](/graph/traverse) +主条目:[在树/图上 DFS](/graph/traverse) diff --git a/docs/search/idastar.md b/docs/search/idastar.md index 769ef809..e727948c 100644 --- a/docs/search/idastar.md +++ b/docs/search/idastar.md @@ -1,8 +1,8 @@ -学习 IDA\* 之前,请确保您已经学完了 [A\*](/search/astar) 算法和 [迭代加深搜索](/search/iterative) 。 +学习 IDA\*之前,请确保您已经学完了[A\*](/search/astar)算法和[迭代加深搜索](/search/iterative)。 -## IDA\* 简介 +## IDA\*简介 -IDA\*,即采用迭代加深的 A\* 算法。相对于 A\* 算法,由于 IDA\* 改成了深度优先的方式,所以 IDA\* 更实用: +IDA\*,即采用迭代加深的 A\*算法。相对于 A\*算法,由于 IDA\*改成了深度优先的方式,所以 IDA\*更实用: 1. 不需要判重,不需要排序; 2. 空间需求减少。 @@ -43,8 +43,7 @@ end; ## 例题 -??? note "埃及分数" - **题目描述** +??? note "埃及分数"**题目描述** 在古埃及,人们使用单位分数的和(即 $\frac{1}{a}$,$a$ 是自然数)表示一切有理数。例如,$\frac{2}{3}=\frac{1}{2}+\frac{1}{6}$,但不允许 $\frac{2}{3}=\frac{1}{3}+\frac{1}{3}$,因为在加数中不允许有相同的。 @@ -66,15 +65,15 @@ end; **分析** -这道题目理论上可以用回溯法求解,但是**解答树**会非常 “恐怖”—不仅深度没有明显的上界,而且加数的选择理论上也是无限的。换句话说,如果用宽度优先遍历,连一层都扩展不完,因为每一层都是**无限大**的。 +这道题目理论上可以用回溯法求解,但是**解答树**会非常“恐怖”—不仅深度没有明显的上界,而且加数的选择理论上也是无限的。换句话说,如果用宽度优先遍历,连一层都扩展不完,因为每一层都是**无限大**的。 -解决方案是采用迭代加深搜索:从小到大枚举深度上限 $maxd$,每次执行只考虑深度不超过 $maxd$ 的节点。这样,只要解的深度优先,则一定可以在有限时间内枚举到。 +解决方案是采用迭代加深搜索:从小到大枚举深度上限 $maxd$ ,每次执行只考虑深度不超过 $maxd$ 的节点。这样,只要解的深度优先,则一定可以在有限时间内枚举到。 -深度上限 $maxd$ 还可以用来**剪枝**。 按照分母递增的顺序来进行扩展,如果扩展到 i 层时,前 $i$ 个分数之和为 $\frac{c}{d}$,而第 $i$ 个分数为 $\frac{1}{e}$ ,则接下来至少还需要 $\frac{\frac{a}{b}-\frac{c}{d}}{\frac{1}{e}}$ 个分数,总和才能达到 $\frac{a}{b}$ 。 例如,当前搜索到 $\frac{19}{45}=\frac{1}{5}+\frac{1}{100}+\cdots$ ,则后面的分数每个最大为 $\frac{1}{101}$,至少需要 $\frac{\frac{19}{45}-\frac{1}{5}}{\frac{1}{101}}=23$ 项总和才能达到 $\frac{19}{45}$ ,因此前 $22$ 次迭代是根本不会考虑这棵子树的。这里的关键在于:可以估计至少还要多少步才能出解。 +深度上限 $maxd$ 还可以用来**剪枝**。按照分母递增的顺序来进行扩展,如果扩展到 i 层时,前 $i$ 个分数之和为 $\frac{c}{d}$ ,而第 $i$ 个分数为 $\frac{1}{e}$ ,则接下来至少还需要 $\frac{\frac{a}{b}-\frac{c}{d}}{\frac{1}{e}}$ 个分数,总和才能达到 $\frac{a}{b}$ 。例如,当前搜索到 $\frac{19}{45}=\frac{1}{5}+\frac{1}{100}+\cdots$ ,则后面的分数每个最大为 $\frac{1}{101}$ ,至少需要 $\frac{\frac{19}{45}-\frac{1}{5}}{\frac{1}{101}}=23$ 项总和才能达到 $\frac{19}{45}$ ,因此前 $22$ 次迭代是根本不会考虑这棵子树的。这里的关键在于:可以估计至少还要多少步才能出解。 -注意,这里的估计都是乐观的,因为用了**至少**这个词。 说得学术一点,设深度上限为 $maxd$,当前结点 $n$ 的深度为 $g(n)$,乐观估价函数为 $h(n)$,则当 $g(n)+h(n)>maxd$ 时应该剪枝。 这样的算法就是 IDA\*。 当然,在实战中不需要严格地在代码里写出 $g(n)$ 和 $h(n)$ ,只需要像刚才那样设计出乐观估价函数,想清楚在什么情况下不可能在当前的深度限制下出解即可。 +注意,这里的估计都是乐观的,因为用了**至少**这个词。说得学术一点,设深度上限为 $maxd$ ,当前结点 $n$ 的深度为 $g(n)$ ,乐观估价函数为 $h(n)$ ,则当 $g(n)+h(n)>maxd$ 时应该剪枝。这样的算法就是 IDA\*。当然,在实战中不需要严格地在代码里写出 $g(n)$ 和 $h(n)$ ,只需要像刚才那样设计出乐观估价函数,想清楚在什么情况下不可能在当前的深度限制下出解即可。 -> 如果可以设计出一个乐观估价函数,预测从当前结点至少还需要扩展几层结点才有可能得到解,则迭代加深搜索变成了 IDA\* 算法。 +> 如果可以设计出一个乐观估价函数,预测从当前结点至少还需要扩展几层结点才有可能得到解,则迭代加深搜索变成了 IDA\*算法。 **代码** diff --git a/docs/search/index.md b/docs/search/index.md index 66b32a65..dce97aae 100644 --- a/docs/search/index.md +++ b/docs/search/index.md @@ -18,11 +18,11 @@ 从状态图上起点和终点同时开始进行宽度优先搜索,如果发现相遇了,那么可以认为是获得了可行解。 -## A\* 搜索 +## A\*搜索 主条目:[A\*](/search/astar/) -## IDA\* 搜索 +## IDA\*搜索 主条目:[IDA\*](/search/idastar/) @@ -38,7 +38,7 @@ ### 调整法 -通过对子树的比较剪掉重复子树和明显不是最有 “前途” 的子树。 +通过对子树的比较剪掉重复子树和明显不是最有“前途”的子树。 ### 数学方法 @@ -48,9 +48,8 @@ 也称折半搜索,主要思想是分治,通过将枚举量减少到原来的一半和特殊的合并技巧以使情况数减少到原来的 sqrt,复杂度也就开了个方,折半搜索也是一个很好的优化,往往能在 OI 竞赛中获得出人意料的效果(尤其在面对数据水的时候) -所谓 **meet-in-middle** , 就是让 dfs 的状态在中间的时候碰面。我们知道, 如果一个暴力 dfs 有 $K$ 个转移, 那么它的时间复杂度 (大多数情况) 是 $O(K^N)$ 的。那我们就想, 当 $N$ 到达一定程度时, TLE 会变成必然。 +所谓**meet-in-middle**, 就是让 dfs 的状态在中间的时候碰面。我们知道,如果一个暴力 dfs 有 $K$ 个转移,那么它的时间复杂度(大多数情况)是 $O(K^N)$ 的。那我们就想,当 $N$ 到达一定程度时,TLE 会变成必然。 -例题 [luogu P2962 \[USACO09NOV\]灯 Lights](https://www.luogu.org/problemnew/show/P2962) +例题[luogu P2962\[USACO09NOV\]灯 Lights](https://www.luogu.org/problemnew/show/P2962) -我们正常想, 如果这道题暴力 dfs 找开关灯的状态, 时间复杂度就是 $O(2^{35})$, 显然超时。不过, 如果我们用 **meet-in-middle** 的话, 时间复杂度将会变为 $O(2^{18} \times 2)$ 而已。 - **meet-in-middle** 就是让我们先找一半的状态, 也就是 $1$ 到 $mid$ ($N$ 的一半) 的状态, 再找剩下的状态就可以了。我们把前半段的状态全部存储在 $hash$ 表或者 $map$ 里面, 然后在找后半段的状态的时候, 先判断后半段是不是都合法, 就可以判断上半段有没有配对的上半段使得整段合法。 +我们正常想,如果这道题暴力 dfs 找开关灯的状态,时间复杂度就是 $O(2^{35})$ , 显然超时。不过,如果我们用**meet-in-middle**的话,时间复杂度将会变为 $O(2^{18} \times 2)$ 而已。**meet-in-middle**就是让我们先找一半的状态,也就是 $1$ 到 $mid$ ( $N$ 的一半)的状态,再找剩下的状态就可以了。我们把前半段的状态全部存储在 $hash$ 表或者 $map$ 里面,然后在找后半段的状态的时候,先判断后半段是不是都合法,就可以判断上半段有没有配对的上半段使得整段合法。 diff --git a/docs/search/iterative.md b/docs/search/iterative.md index 8d234581..577e5599 100644 --- a/docs/search/iterative.md +++ b/docs/search/iterative.md @@ -2,7 +2,7 @@ 迭代加深是一种**每次限制搜索深度的**深度优先搜索。 -它的本质还是深度优先搜索,只不过在搜索的同时带上了一个深度 $d$,当 $d$ 达到设定的深度时就返回,一般用于找最优解。如果一次搜索没有找到合法的解,就让设定的深度 $+1$,重新从根开始。 +它的本质还是深度优先搜索,只不过在搜索的同时带上了一个深度 $d$ ,当 $d$ 达到设定的深度时就返回,一般用于找最优解。如果一次搜索没有找到合法的解,就让设定的深度 $+1$ ,重新从根开始。 既然是为了找最优解,为什么不用 BFS 呢?我们知道 BFS 的基础是一个队列,队列的空间复杂度很大,当状态比较多或者单个状态比较大时,使用队列的 BFS 就显出了劣势。事实上,迭代加深就类似于用 DFS 方式实现的 BFS,它的空间复杂度相对较小。 @@ -10,7 +10,7 @@ ## 步骤 -首先设定一个较小的深度作为全局变量,进行 DFS。每进入一次 DFS,将当前深度 $d++$,当发现 $d$ 大于设定的深度就返回。如果在搜索的途中发现了答案就可以回溯,同时在回溯的过程中可以记录路径。如果没有发现答案,就返回到函数入口,增加设定深度,继续搜索。 +首先设定一个较小的深度作为全局变量,进行 DFS。每进入一次 DFS,将当前深度 $d++$ ,当发现 $d$ 大于设定的深度就返回。如果在搜索的途中发现了答案就可以回溯,同时在回溯的过程中可以记录路径。如果没有发现答案,就返回到函数入口,增加设定深度,继续搜索。 ## 代码结构 diff --git a/docs/search/optimization.md b/docs/search/optimization.md index 4ca909b6..6247d3dc 100644 --- a/docs/search/optimization.md +++ b/docs/search/optimization.md @@ -2,7 +2,7 @@ dfs(即 深搜)是一种常见的算法,大部分的题目都可以用 dfs 解决,但是大部分情况下,这都是骗分算法,很少会有爆搜为正解的题目。因为 dfs 的时间复杂度特别高。(没学过 dfs 的请自行补上这一课) -既然不能成为正解,那就多骗一点分吧。那么这一篇文章将介绍一些实用的优化算法(俗称 “剪枝”)。 +既然不能成为正解,那就多骗一点分吧。那么这一篇文章将介绍一些实用的优化算法(俗称“剪枝”)。 先来一段深搜模板,之后的模板将在此基础上进行修改。 @@ -19,7 +19,7 @@ void dfs(传入数值) { } ``` -其中的 ans 可以是解的记录,那么从当前解与已有解中选最优就变成了输出解. +其中的 ans 可以是解的记录,那么从当前解与已有解中选最优就变成了输出解。 ## 优化与剪枝 diff --git a/docs/string/ac-automaton.md b/docs/string/ac-automaton.md index 18bb37ac..f963bdfa 100644 --- a/docs/string/ac-automaton.md +++ b/docs/string/ac-automaton.md @@ -16,21 +16,21 @@ trans[next[i]][x], & \text{else} \end{cases} $$ -(约定 $next[0]=0$) +(约定 $next[0]=0$ ) -我们发现 $trans[i]$ 只依赖于之前的值,所以可以跟 [KMP](/string/kmp) 一起求出来 +我们发现 $trans[i]$ 只依赖于之前的值,所以可以跟[KMP](/string/kmp)一起求出来 -时间和空间复杂度:$O(m|∑|)$ +时间和空间复杂度: $O(m|∑|)$ -一些细节:走到接受状态之后立即转移到该状态的 $next$ +一些细节:走到接受状态之后立即转移到该状态的 $next$ ## AC 算法就是 Trie 上的自动机 -注意在 [BFS](/search/bfs) 的同时求出 $trans$ 即可 +注意在[BFS](/search/bfs)的同时求出 $trans$ 即可 可以解决多串匹配问题 -注意细节:$O(n+匹配次数)$ 还是 $O(n)$ +注意细节: $O(n+匹配次数)$ 还是 $O(n)$ 前者能找到每次匹配,后者只能求出匹配次数(通过合并接受状态) diff --git a/docs/string/hash.md b/docs/string/hash.md index 4683da8e..0a7f167b 100644 --- a/docs/string/hash.md +++ b/docs/string/hash.md @@ -1,4 +1,4 @@ -在介绍 Hash 算法之前,首先你需要了解关于 [字符串匹配](/string/match) 的事情。 +在介绍 Hash 算法之前,首先你需要了解关于[字符串匹配](/string/match)的事情。 ## Hash 的思想 @@ -8,7 +8,7 @@ Hash 的核心思想在于,暴力算法中,单次比较的时间太长了, 是比较第一个?最后一个?随便选一个?求所有字符的和? -我们定义一个把 string 映射成 int 的函数 $f$,这个 $f$ 称为是 Hash 函数。 +我们定义一个把 string 映射成 int 的函数 $f$ ,这个 $f$ 称为是 Hash 函数。 我们希望这个函数 $f$ 可以方便地帮我们判断两个字符串是否相等。 @@ -20,7 +20,7 @@ Hash 的核心思想在于,暴力算法中,单次比较的时间太长了, 时间复杂度和 Hash 的准确率。 -通常我们采用的是多项式 Hash 的方法,即 $\operatorname{f}(s) = \sum s[i] \times b^i \pmod M$ +通常我们采用的是多项式 Hash 的方法,即 $\operatorname{f}(s) = \sum s[i] \times b^i \pmod M$ 这里面的 $b$ 和 $M$ 需要选取得足够合适才行,以使得 Hash 的冲突尽量均匀。 @@ -64,25 +64,25 @@ match(char *a, char *b, int n, int m) { 改进时间复杂度或者错误率 -在上述例子中,时间复杂度已经是 $O(n+m)$ +在上述例子中,时间复杂度已经是 $O(n+m)$ 我们来分析错误率 -由于 $n >> m$,要进行约 $n$ 次比较,每次错误率 $\frac1{M}$ ,那么总错误率是? +由于 $n >> m$ ,要进行约 $n$ 次比较,每次错误率 $\frac1{M}$ ,那么总错误率是? 先补充一些随机数学的知识(非严格地) -现在考虑这 $n$ 次比较,如果看成独立的,总错误率 $1-(1-1/M)^n$ +现在考虑这 $n$ 次比较,如果看成独立的,总错误率 $1-(1-1/M)^n$ -当 $M >> n$ 时,总错误率接近于$\frac{n}{M}$ +当 $M >> n$ 时,总错误率接近于 $\frac{n}{M}$ -当 $M = n$ 时,接近于 $1-\frac{1}{e} (≈0.63)$ +当 $M = n$ 时,接近于 $1-\frac{1}{e} (≈0.63)$ -如果不是独立的,最坏情况也就是全部加起来,等于 $\frac{n}{M}$ +如果不是独立的,最坏情况也就是全部加起来,等于 $\frac{n}{M}$ -要改进错误率,可以增加 $M$ +要改进错误率,可以增加 $M$ -选取一个大的 $M$,或者两个互质的小的 $M$ +选取一个大的 $M$ ,或者两个互质的小的 $M$ 时间复杂度不变,单次错误率平方 @@ -92,7 +92,7 @@ match(char *a, char *b, int n, int m) { 假设有个程序要对 $10^{18}$ 大小的数组进行操作,保证只有 $10^6$ 个元素被访问到 -由于存不下,我们在每次操作时,对下标取 Hash 值(比如,直接 $\bmod M$),然后在 $M$ 大小的数组内操作 +由于存不下,我们在每次操作时,对下标取 Hash 值(比如,直接 $\bmod M$ ),然后在 $M$ 大小的数组内操作 如果冲突了怎么办? diff --git a/docs/string/index.md b/docs/string/index.md index dfe3e0b0..074cf1be 100644 --- a/docs/string/index.md +++ b/docs/string/index.md @@ -6,22 +6,22 @@ 字符集是符号和文字组成的集合,在 OI 中,处理字符串时计算复杂度往往要考虑到字符集大小带来的常数影响。 -举个栗子,如果一道题只包含'A' ~ 'Z' 意味着字符集大小是 26 。 如果再加上 '0' ~ '9' 字符集大小就变成了 36 +举个栗子,如果一道题只包含'A' ~ 'Z' 意味着字符集大小是 26。如果再加上 '0' ~ '9' 字符集大小就变成了 36 计算复杂度时,字符集大小带来的常数往往要用 $\alpha$ 表示。 ## 如何存字符串 -可以开一个 `char` 数组 , 如 `char a[100]` +可以开一个 `char` 数组 , 如 `char a[100]` -也可以用 `vector` 如 `vector v` +也可以用 `vector` 如 `vector v` 同时 STL 中也提供了字符串容器 `std :: string` -另外,在 `C / C++` 中也可以声明字符串字面量,比如 `char *buf = "XD"`。 +另外,在 `C/C++` 中也可以声明字符串字面量,比如 `char *buf = "XD"` 。 ## 字符串存储的位置 - 字符串字面量:它们的值在编译过程中已经确定,保存在可执行目标文件的 `.rodata` 段内。 调用 `objdump -s -j .rodata 文件名` 可以查看 `.rodata` 段的具体内容。 -- 字符数组、`vector`、`string`:局部变量保存在栈中,全局变量若初始化为非 0 值则保存在可执行目标文件的 `.data` 段内,若未初始化或初始化为 0 则保存在 `.bss` 段。 +- 字符数组、 `vector` 、 `string` :局部变量保存在栈中,全局变量若初始化为非 0 值则保存在可执行目标文件的 `.data` 段内,若未初始化或初始化为 0 则保存在 `.bss` 段。 diff --git a/docs/string/lib-func.md b/docs/string/lib-func.md index 657a293f..678e6c16 100644 --- a/docs/string/lib-func.md +++ b/docs/string/lib-func.md @@ -1,60 +1,60 @@ -# C / C++ 标准库中的字符串 +# C/C++ 标准库中的字符串 ## C 标准库 C 标准库是在对字符数组进行操作 -### `strlen` +### `strlen` -`int strlen(const char *str)` :返回从 `str[0]` 开始直到 `'\0'` 的字符数。注意,未开启 O2 优化时,该操作写在循环条件中复杂度是 $\Theta(N)$ 的。 + `int strlen(const char *str)` :返回从 `str[0]` 开始直到 `'\0'` 的字符数。注意,未开启 O2 优化时,该操作写在循环条件中复杂度是 $\Theta(N)$ 的。 -### `printf` +### `printf` -`printf("%s", s)`:用 `%s` 来输出一个字符串(字符数组)。 + `printf("%s", s)` :用 `%s` 来输出一个字符串(字符数组)。 -### `scanf` +### `scanf` -`scanf("%s", s)`:用 `%s` 来读入一个字符串(字符数组)。 + `scanf("%s", s)` :用 `%s` 来读入一个字符串(字符数组)。 -### `sscanf` +### `sscanf` -`sscanf(const char *__source, const char *__format, ...)`:从字符串`__source`里读取变量,比如`sscanf(str,"%d",&a)`。 + `sscanf(const char *__source, const char *__format, ...)` :从字符串 `__source` 里读取变量,比如 `sscanf(str,"%d",&a)` 。 -### `sprintf` +### `sprintf` -`sprintf(char *__stream, const char *__format, ...)`:将`__format`字符串里的内容输出到`__stream`中,比如`sprintf(str,"%d",i)`。 + `sprintf(char *__stream, const char *__format, ...)` :将 `__format` 字符串里的内容输出到 `__stream` 中,比如 `sprintf(str,"%d",i)` 。 -### `strcmp` +### `strcmp` -`int strcmp(const char *str1, const char *str2)`:按照字典序比较 `str1 str2` 若 `str1` 字典序小返回负值, 一样返回 0 ,大返回正值 请注意,不要简单的认为只有 `0, 1, -1` 三种,在不同平台下的返回值都遵循正负,但并非都是 `0, 1, -1` + `int strcmp(const char *str1, const char *str2)` :按照字典序比较 `str1 str2` 若 `str1` 字典序小返回负值,一样返回 0,大返回正值 请注意,不要简单的认为只有 `0, 1, -1` 三种,在不同平台下的返回值都遵循正负,但并非都是 `0, 1, -1` -### `strcpy` +### `strcpy` -`char *strcpy(char *str, const char *src)` : 把 `src` 中的字符复制到 `str` 中, `str` `src` 均为字符数组头指针, 返回值为 `str` 包含空终止符号 `'\0'` 。 + `char *strcpy(char *str, const char *src)` : 把 `src` 中的字符复制到 `str` 中, `str` `src` 均为字符数组头指针,返回值为 `str` 包含空终止符号 `'\0'` 。 -### `strncpy` +### `strncpy` -`char *strncpy(char *str, const char *src, int cnt)` :复制至多 `cnt` 个字符到 `str` 中,若 `src` 终止而数量未达 `cnt` 则写入空字符到 `str` 直至写入总共 `cnt` 个字符。 + `char *strncpy(char *str, const char *src, int cnt)` :复制至多 `cnt` 个字符到 `str` 中,若 `src` 终止而数量未达 `cnt` 则写入空字符到 `str` 直至写入总共 `cnt` 个字符。 -### `strcat` +### `strcat` -`char *strcat(char *str1, const char *str2)` : 将 `str2` 接到 `str1` 的结尾,用 `*str2` 替换 `str1` 末尾的 `'\0'` 返回 `str1` 。 + `char *strcat(char *str1, const char *str2)` : 将 `str2` 接到 `str1` 的结尾,用 `*str2` 替换 `str1` 末尾的 `'\0'` 返回 `str1` 。 -### `strstr` +### `strstr` -### `strchr` +### `strchr` -### `strrchr` +### `strrchr` ## C++ 标准库 C++ 标准库是在对字符串对象进行操作,同时也提供对字符数组的兼容。 -### `std::string` +### `std::string` -- 赋值运算符 `=` 右侧可以是 `const string / string / const char* / char*`。 +- 赋值运算符 `=` 右侧可以是 `const string/string/const char*/char*` 。 - 访问运算符 `[cur]` 返回 `cur` 位置的引用。 -- 访问函数 `data() / c_str()` 返回一个`const char*` 指针, 内容与该 `string` 相同。 +- 访问函数 `data()/c_str()` 返回一个 `const char*` 指针,内容与该 `string` 相同。 - 容量函数 `size()` 返回字符串字符个数。 - 还有一些其他的函数如 `find()` 找到并返回某字符位置。 -- `std :: string` 重载了比较逻辑运算符,复杂度是 $\Theta(N)$ 的。 +- `std :: string` 重载了比较逻辑运算符,复杂度是 $\Theta(N)$ 的。 diff --git a/docs/string/manacher.md b/docs/string/manacher.md index b1530d30..d68cb939 100644 --- a/docs/string/manacher.md +++ b/docs/string/manacher.md @@ -1,28 +1,28 @@ ## 描述 -给定一个长度为 $n$ 的字符串 $s$,请找到所有对 $(i, j)$ 使得子串 $s[i \dots j]$ 为一个回文串。当 $t = t_{\text{rev}}$ 时,字符串 $t$ 是一个回文串($t_{\text{rev}}$ 是 $t$ 的反转字符串)。 +给定一个长度为 $n$ 的字符串 $s$ ,请找到所有对 $(i, j)$ 使得子串 $s[i \dots j]$ 为一个回文串。当 $t = t_{\text{rev}}$ 时,字符串 $t$ 是一个回文串( $t_{\text{rev}}$ 是 $t$ 的反转字符串)。 ## 更进一步的描述 显然在最坏情况下可能有 $O(n^2)$ 个回文串,因此似乎一眼看过去该问题并没有线性算法。 -但是关于回文串的信息可用**一种更紧凑的方式**表达:对于每个位置 $i = 0 \dots n - 1$,我们找出值 $d_1[i]$ 和 $d_2[i]$。二者分别表示以位置 $i$ 为中心的长度为奇数和长度为偶数的回文串个数。 +但是关于回文串的信息可用**一种更紧凑的方式**表达:对于每个位置 $i = 0 \dots n - 1$ ,我们找出值 $d_1[i]$ 和 $d_2[i]$ 。二者分别表示以位置 $i$ 为中心的长度为奇数和长度为偶数的回文串个数。 -举例来说,字符串 $s = \mathtt{abababc}$ 以 $s[3] = b$ 为中心有三个奇数长度的回文串,也即 $d_1[3] = 3$: +举例来说,字符串 $s = \mathtt{abababc}$ 以 $s[3] = b$ 为中心有三个奇数长度的回文串,也即 $d_1[3] = 3$ : $$ a\ \overbrace{b\ a\ \underset{s_3}{b}\ a\ b}^{d_1[3]=3}\ c $$ -字符串 $s = \mathtt{cbaabd}$ 以 $s[3] = a$ 为中心有两个偶数长度的回文串,也即 $d_2[3] = 2$: +字符串 $s = \mathtt{cbaabd}$ 以 $s[3] = a$ 为中心有两个偶数长度的回文串,也即 $d_2[3] = 2$ : $$ c\ \overbrace{b\ a\ \underset{s_3}{a}\ b}^{d_2[3]=2}\ d $$ -因此关键思路是,如果以某个位置 $i$ 为中心,我们有一个长度为 $l$ 的回文串,那么我们有以 $i$ 为中心的长度为 $l - 2$,$l - 4$,等等的回文串。所以 $d_1[i]$ 和 $d_2[i]$ 两个数组已经足够表示字符串中所有子回文串的信息。 +因此关键思路是,如果以某个位置 $i$ 为中心,我们有一个长度为 $l$ 的回文串,那么我们有以 $i$ 为中心的长度为 $l - 2$ , $l - 4$ ,等等的回文串。所以 $d_1[i]$ 和 $d_2[i]$ 两个数组已经足够表示字符串中所有子回文串的信息。 -一个令人惊讶的事实是,存在一个复杂度为线性并且足够简单的算法计算上述两个 “回文性质数组” $d_1[]$ 和 $d_2[]$。在这篇文章中我们将详细的描述该算法。 +一个令人惊讶的事实是,存在一个复杂度为线性并且足够简单的算法计算上述两个“回文性质数组” $d_1[]$ 和 $d_2[]$ 。在这篇文章中我们将详细的描述该算法。 ## 解法 @@ -32,9 +32,9 @@ $$ ## 朴素算法 -为了避免在之后的叙述中出现歧义,这里我们指出什么是 “朴素算法”。 +为了避免在之后的叙述中出现歧义,这里我们指出什么是“朴素算法”。 -该算法通过下述方式工作:对每个中心位置 $i$,在比较一对对应字符后,只要可能,该算法便尝试将答案加 $1$。 +该算法通过下述方式工作:对每个中心位置 $i$ ,在比较一对对应字符后,只要可能,该算法便尝试将答案加 $1$ 。 该算法是比较慢的:它只能在 $O(n^2)$ 的时间内计算答案。 @@ -58,17 +58,17 @@ for (int i = 0; i < n; i++) { ## Manacher 算法 -这里我们将只描述算法中寻找所有奇数长度子回文串的情况,即只计算 $d_1[]$;寻找所有偶数长度子回文串的算法(即计算数组 $d_2[]$)将只需对奇数情况下的算法进行一些小修改。 +这里我们将只描述算法中寻找所有奇数长度子回文串的情况,即只计算 $d_1[]$ ;寻找所有偶数长度子回文串的算法(即计算数组 $d_2[]$ )将只需对奇数情况下的算法进行一些小修改。 -为了快速计算,我们维护已找到的子回文串的最靠右的**边界 $(l, r)$**(即具有最大 $r$ 值的回文串)。初始时,我们置 $l = 0$ 和 $r = -1$。 +为了快速计算,我们维护已找到的子回文串的最靠右的**边界 $(l, r)$ **(即具有最大 $r$ 值的回文串)。初始时,我们置 $l = 0$ 和 $r = -1$ 。 -现在假设我们要对下一个 $i$ 计算 $d_1[i]$,而之前所有 $d_1[]$ 中的值已计算完毕。我们将通过下列方式计算: +现在假设我们要对下一个 $i$ 计算 $d_1[i]$ ,而之前所有 $d_1[]$ 中的值已计算完毕。我们将通过下列方式计算: -- 如果 $i$ 位于当前子回文串之外,即 $i > r$,那么我们调用朴素算法。 +- 如果 $i$ 位于当前子回文串之外,即 $i > r$ ,那么我们调用朴素算法。 - 因此我们将连续的增加 $d_1[i]$,同时在每一步中检查当前的子串 $[i - d_1[i] \dots i + d_1[i]]$ 是否为一个回文串。如果我们找到了第一处对应字符不同,又或者碰到了 $s$ 的边界,则算法停止。在两种情况下我们均已计算完 $d_1[i]$。此后,仍需记得更新 $(l, r)$。 + 因此我们将连续的增加 $d_1[i]$ ,同时在每一步中检查当前的子串 $[i - d_1[i] \dots i + d_1[i]]$ 是否为一个回文串。如果我们找到了第一处对应字符不同,又或者碰到了 $s$ 的边界,则算法停止。在两种情况下我们均已计算完 $d_1[i]$ 。此后,仍需记得更新 $(l, r)$ 。 -- 现在考虑 $i \le r$ 的情况。我们将尝试从已计算过的 $d_1[]$ 的值中获取一些信息。首先在子回文串 $(l, r)$ 中反转位置 $i$,即我们得到 $j = l + (r - i)$。现在来考察值 $d_1[j]$。因为位置 $j$ 同位置 $i$ 对称,我们**几乎总是**可以置 $d_1[i] = d_1[j]$。该想法的图示如下(可认为以 $j$ 为中心的回文串被 “拷贝” 至以 $i$ 为中心的位置上): +- 现在考虑 $i \le r$ 的情况。我们将尝试从已计算过的 $d_1[]$ 的值中获取一些信息。首先在子回文串 $(l, r)$ 中反转位置 $i$ ,即我们得到 $j = l + (r - i)$ 。现在来考察值 $d_1[j]$ 。因为位置 $j$ 同位置 $i$ 对称,我们**几乎总是**可以置 $d_1[i] = d_1[j]$ 。该想法的图示如下(可认为以 $j$ 为中心的回文串被“拷贝”至以 $i$ 为中心的位置上): $$ \ldots\ @@ -86,11 +86,11 @@ for (int i = 0; i < n; i++) { \ldots $$ - 然而有一个**棘手的情况**需要被正确处理:当 “内部” 的回文串到达 “外部” 回文串的边界时,即 $j - d_1[j] + 1 \le l$(或者等价的说,$i + d_1[j] - 1 \ge r$)。因为在 “外部” 回文串范围以外的对称性没有保证,因此直接置 $d_1[i] = d_1[j]$ 将是不正确的:我们没有足够的信息来断言在位置 $i$ 的回文串具有同样的长度。 + 然而有一个**棘手的情况**需要被正确处理:当“内部”的回文串到达“外部”回文串的边界时,即 $j - d_1[j] + 1 \le l$ (或者等价的说, $i + d_1[j] - 1 \ge r$ )。因为在“外部”回文串范围以外的对称性没有保证,因此直接置 $d_1[i] = d_1[j]$ 将是不正确的:我们没有足够的信息来断言在位置 $i$ 的回文串具有同样的长度。 - 实际上,为了正确处理这种情况,我们应该 “截断” 回文串的长度,即置 $d_1[i] = r - i$。之后我们将运行朴素算法以尝试尽可能增加 $d_1[i]$ 的值。 + 实际上,为了正确处理这种情况,我们应该“截断”回文串的长度,即置 $d_1[i] = r - i$ 。之后我们将运行朴素算法以尝试尽可能增加 $d_1[i]$ 的值。 - 该种情况的图示如下(以 $j$ 为中心的回文串已经被截断以落在 “外部” 回文串内): + 该种情况的图示如下(以 $j$ 为中心的回文串已经被截断以落在“外部”回文串内): $$ \ldots\ @@ -108,9 +108,9 @@ for (int i = 0; i < n; i++) { }_\text{try moving here} $$ - 该图示显示出,尽管以 $j$ 为中心的回文串可能更长,以致于超出 “外部” 回文串,但在位置 $i$,我们只能利用其完全落在 “外部” 回文串内的部分。然而位置 $i$ 的答案可能比这个值更大,因此接下来我们将运行朴素算法来尝试将其扩展至 “外部” 回文串之外,也即标识为 "try moving here" 的区域。 + 该图示显示出,尽管以 $j$ 为中心的回文串可能更长,以致于超出“外部”回文串,但在位置 $i$ ,我们只能利用其完全落在“外部”回文串内的部分。然而位置 $i$ 的答案可能比这个值更大,因此接下来我们将运行朴素算法来尝试将其扩展至“外部”回文串之外,也即标识为 "try moving here" 的区域。 -最后,仍有必要提醒的是,我们应当记得在计算完每个 $d_1[i]$ 后更新值 $(l, r)$。 +最后,仍有必要提醒的是,我们应当记得在计算完每个 $d_1[i]$ 后更新值 $(l, r)$ 。 同时,再让我们重复一遍:计算偶数长度回文串数组 $d_2[]$ 的算法同上述计算奇数长度回文串数组 $d_1[]$ 的算法十分类似。 @@ -120,15 +120,15 @@ for (int i = 0; i < n; i++) { 然而更仔细的分析显示出该算法具有线性复杂度。此处我们需要指出,[计算 Z 函数的算法](z-function.md)和该算法较为类似,并同样具有线性时间复杂度。 -实际上,注意到朴素算法的每次迭代均会使 $r$ 增加 $1$,以及 $r$ 在算法运行过程中从不减小。这两个观察告诉我们朴素算法总共会进行 $O(n)$ 次迭代。 +实际上,注意到朴素算法的每次迭代均会使 $r$ 增加 $1$ ,以及 $r$ 在算法运行过程中从不减小。这两个观察告诉我们朴素算法总共会进行 $O(n)$ 次迭代。 -Manacher 算法的另一部分显然也是线性的,因此总复杂度为 $O(n)$。 +Manacher 算法的另一部分显然也是线性的,因此总复杂度为 $O(n)$ 。 ## Manacher 算法的实现 ### 分类讨论 -为了计算 $d_1[]$,我们有以下代码: +为了计算 $d_1[]$ ,我们有以下代码: ```c++ vector d1(n); @@ -166,21 +166,21 @@ for (int i = 0, l = 0, r = -1; i < n; i++) { 虽然在讲解过程及上述实现中我们将 $d_1[]$ 和 $d_2[]$ 的计算分开考虑,但实际上可以通过一个技巧将二者的计算统一为 $d_1[]$ 的计算。 -给定一个长度为 $n$ 的字符串 $s$,我们在其 $n + 1$ 个空中插入分隔符 $\#$,从而构造一个长度为 $2n + 1$ 的字符串 $s'$。举例来说,对于字符串 $s = \mathtt{abababc}$,其对应的 $s' = \mathtt{\#a\#b\#a\#b\#a\#b\#c\#}$。 +给定一个长度为 $n$ 的字符串 $s$ ,我们在其 $n + 1$ 个空中插入分隔符 $\#$ ,从而构造一个长度为 $2n + 1$ 的字符串 $s'$ 。举例来说,对于字符串 $s = \mathtt{abababc}$ ,其对应的 $s' = \mathtt{\#a\#b\#a\#b\#a\#b\#c\#}$ 。 -对于字母间的 $\#$,其实际意义为 $s$ 中对应的 “空”。而两端的 $\#$ 则是为了实现的方便。 +对于字母间的 $\#$ ,其实际意义为 $s$ 中对应的“空”。而两端的 $\#$ 则是为了实现的方便。 -注意到,在对 $s'$ 计算 $d_1[]$ 后,对于一个位置 $i$,$d_1[i]$ 所描述的最长的子回文串必定以 $\#$ 结尾(若以字母结尾,由于字母两侧必定各有一个 $\#$,因此可向外扩展一个得到一个更长的)。因此,对于 $s$ 中一个以字母为中心的极大子回文串,设其长度为 $m + 1$,则其在 $s'$ 中对应一个以相应字母为中心,长度为 $2m + 3$ 的极大子回文串;而对于 $s$ 中一个以空为中心的极大子回文串,设其长度为 $m$,则其在 $s'$ 中对应一个以相应表示空的 $\#$ 为中心,长度为 $2m + 1$ 的极大子回文串(上述两种情况下的 $m$ 均为偶数,但该性质成立与否并不影响结论)。综合以上观察及少许计算后易得,在 $s'$ 中,$d_1[i]$ 表示在 $s​$ 中以对应位置为中心的极大子回文串的**总长度加一**。 +注意到,在对 $s'$ 计算 $d_1[]$ 后,对于一个位置 $i$ , $d_1[i]$ 所描述的最长的子回文串必定以 $\#$ 结尾(若以字母结尾,由于字母两侧必定各有一个 $\#$ ,因此可向外扩展一个得到一个更长的)。因此,对于 $s$ 中一个以字母为中心的极大子回文串,设其长度为 $m + 1$ ,则其在 $s'$ 中对应一个以相应字母为中心,长度为 $2m + 3$ 的极大子回文串;而对于 $s$ 中一个以空为中心的极大子回文串,设其长度为 $m$ ,则其在 $s'$ 中对应一个以相应表示空的 $\#$ 为中心,长度为 $2m + 1$ 的极大子回文串(上述两种情况下的 $m$ 均为偶数,但该性质成立与否并不影响结论)。综合以上观察及少许计算后易得,在 $s'$ 中, $d_1[i]$ 表示在 $s​$ 中以对应位置为中心的极大子回文串的**总长度加一**。 上述结论建立了 $s'$ 的 $d_1[]$ 同 $s$ 的 $d_1[]$ 和 $d_2[]$ 间的关系。 -由于该统一处理本质上即求 $s'$ 的 $d_1[]$,因此在得到 $s'$ 后,代码同上节计算 $d_1[]$ 的一样。 +由于该统一处理本质上即求 $s'$ 的 $d_1[]$ ,因此在得到 $s'$ 后,代码同上节计算 $d_1[]$ 的一样。 ## 练习题目 - [UVA #11475 "Extend to Palindrome"](https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=2470) -- [P4555 \[国家集训队\] 最长双回文串](https://www.luogu.org/problemnew/show/P4555) +- [P4555\[国家集训队\]最长双回文串](https://www.luogu.org/problemnew/show/P4555) * * * -**本页面主要译自博文 [Нахождение всех подпалиндромов](http://e-maxx.ru/algo/palindromes_count) 与其英文翻译版 [Finding all sub-palindromes in $O(N)$](https://cp-algorithms.com/string/manacher.html) 。其中俄文版版权协议为 Public Domain + Leave a Link;英文版版权协议为 CC-BY-SA 4.0。** +**本页面主要译自博文[Нахождение всех подпалиндромов](http://e-maxx.ru/algo/palindromes_count)与其英文翻译版[Finding all sub-palindromes in $O(N)$ ](https://cp-algorithms.com/string/manacher.html)。其中俄文版版权协议为 Public Domain + Leave a Link;英文版版权协议为 CC-BY-SA 4.0。** diff --git a/docs/string/match.md b/docs/string/match.md index 2a75efa9..39f0c3e3 100644 --- a/docs/string/match.md +++ b/docs/string/match.md @@ -41,12 +41,12 @@ match(char *a, char *b, int n, int m) { 最好是 $O(n)$ 的。 -如果字符集的大小大于 1 (有至少两个不同的字符),平均时间复杂度是 $O(n)$ 的。 +如果字符集的大小大于 1(有至少两个不同的字符),平均时间复杂度是 $O(n)$ 的。 ## Hash 的方法 -参见 [Hash](/string/hash) +参见[Hash](/string/hash) ## KMP 算法 -参见 [KMP](/string/kmp) +参见[KMP](/string/kmp) diff --git a/docs/string/prefix-function.md b/docs/string/prefix-function.md index 228dfbd9..b291fc56 100644 --- a/docs/string/prefix-function.md +++ b/docs/string/prefix-function.md @@ -1,6 +1,6 @@ ## 前缀函数定义 -给定一个长度为 $n$ 的字符串 $s$,其**前缀函数**被定义为一个长度为 $n$ 的数组 $\pi$。其中 $\pi[i]$ 为既是子串 $s[0\dots i]$ 的前缀同时也是该子串的后缀的最长真前缀(proper prefix)长度。一个字符串的真前缀是其前缀但不等于该字符串自身。根据定义,$\pi[0] = 0$。 +给定一个长度为 $n$ 的字符串 $s$ ,其**前缀函数**被定义为一个长度为 $n$ 的数组 $\pi$ 。其中 $\pi[i]$ 为既是子串 $s[0\dots i]$ 的前缀同时也是该子串的后缀的最长真前缀(proper prefix)长度。一个字符串的真前缀是其前缀但不等于该字符串自身。根据定义, $\pi[0] = 0$ 。 前缀函数的定义可用数学语言描述如下: @@ -8,7 +8,7 @@ $$ \pi[i] = \max_{k = 0 \dots i}\{k: s[0 \dots k - 1] = s[i - (k - 1) \dots i]\} $$ -举例来说,字符串 `abcabcd` 的前缀函数为 $[0, 0, 0, 1, 2, 3, 0]$,字符串 `aabaaab` 的前缀函数为 $[0, 1, 0, 1, 2, 2, 3]$。 +举例来说,字符串 `abcabcd` 的前缀函数为 $[0, 0, 0, 1, 2, 3, 0]$ ,字符串 `aabaaab` 的前缀函数为 $[0, 1, 0, 1, 2, 2, 3]$ 。 ## 朴素算法 @@ -25,7 +25,7 @@ vector prefix_function(string s) { } ``` -显见该算法的时间复杂度为 $O(n^3)$,具有很大的改进空间。 +显见该算法的时间复杂度为 $O(n^3)$ ,具有很大的改进空间。 ## 高效算法 @@ -33,43 +33,43 @@ vector prefix_function(string s) { ### 第一个优化 -第一个重要的观察是相邻的前缀函数值至多增加 $1$。 +第一个重要的观察是相邻的前缀函数值至多增加 $1$ 。 -实际上,如不然,即 $\pi[i + 1] > \pi[i] + 1$,考察长度为 $\pi[i + 1]$ 的 $s[0 \dots i + 1]$ 的后缀可引出矛盾。该后缀去掉最后一个字符后,我们得到一个长度为 $\pi[i + 1] - 1$ 的 $s[0 \dots i]$ 的后缀。该后缀比 $\pi[i]$ 描述的后缀更优,同其定义矛盾。 +实际上,如不然,即 $\pi[i + 1] > \pi[i] + 1$ ,考察长度为 $\pi[i + 1]$ 的 $s[0 \dots i + 1]$ 的后缀可引出矛盾。该后缀去掉最后一个字符后,我们得到一个长度为 $\pi[i + 1] - 1$ 的 $s[0 \dots i]$ 的后缀。该后缀比 $\pi[i]$ 描述的后缀更优,同其定义矛盾。 -下述图例展示了这个矛盾。假定位于位置 $i$ 和 $i + 1$ 的既是后缀同时也是前缀的最长真后缀的长度分别为 $2$ 和 $4$。则字符串 $s_0 s_1 s_2 s_3$ 与字符串 $s_{i - 2} s_{i - 1} s_i s_{i + 1}$ 相同,这意味着 $s_0 s_1 s_2$ 与字符串 $s_{i - 2} s_{i - 1} s_i$ 相同,因此 $\pi[i]$ 至少为 $3$。 +下述图例展示了这个矛盾。假定位于位置 $i$ 和 $i + 1$ 的既是后缀同时也是前缀的最长真后缀的长度分别为 $2$ 和 $4$ 。则字符串 $s_0 s_1 s_2 s_3$ 与字符串 $s_{i - 2} s_{i - 1} s_i s_{i + 1}$ 相同,这意味着 $s_0 s_1 s_2$ 与字符串 $s_{i - 2} s_{i - 1} s_i$ 相同,因此 $\pi[i]$ 至少为 $3$ 。 $$ \underbrace{\overbrace{s_0 ~ s_1}^{\pi[i] = 2} ~ s_2 ~ s_3}_{\pi[i+1] = 4} ~ \dots ~ \underbrace{s_{i-2} ~ \overbrace{s_{i-1} ~ s_{i}}^{\pi[i] = 2} ~ s_{i+1}}_{\pi[i+1] = 4} $$ -所以当移动到下一个位置时,前缀函数的值要么增加一,要么维持不变,要么减少。实际上,该事实已经允许我们将算法时间复杂度减少至 $O(n^2)$。因为每步中前缀函数至多增加 $1$,因此在总的运行过程中,前缀函数至多增加 $n$,同时也至多减小 $n$。这意味着我们仅需要进行 $O(n)$ 次字符串比较,所以总复杂度为 $O(n^2)$。 +所以当移动到下一个位置时,前缀函数的值要么增加一,要么维持不变,要么减少。实际上,该事实已经允许我们将算法时间复杂度减少至 $O(n^2)$ 。因为每步中前缀函数至多增加 $1$ ,因此在总的运行过程中,前缀函数至多增加 $n$ ,同时也至多减小 $n$ 。这意味着我们仅需要进行 $O(n)$ 次字符串比较,所以总复杂度为 $O(n^2)$ 。 ### 第二个优化 让我们走的更远一点:尝试摆脱掉字符串比较。为了达成这一点,我们必须用到先前计算的所有信息。 -现在考虑计算位置 $i + 1$ 的前缀函数 $\pi$ 的值。如果 $s[i + 1] = s[\pi[i]]$,那么我们可以断言 $\pi[i + 1] = \pi[i] + 1$,因为我们已经知道位于位置 $i$ 的长度为 $\pi[i]$ 的后缀同长度为 $\pi[i]$ 的前缀相等。参照下述图例: +现在考虑计算位置 $i + 1$ 的前缀函数 $\pi$ 的值。如果 $s[i + 1] = s[\pi[i]]$ ,那么我们可以断言 $\pi[i + 1] = \pi[i] + 1$ ,因为我们已经知道位于位置 $i$ 的长度为 $\pi[i]$ 的后缀同长度为 $\pi[i]$ 的前缀相等。参照下述图例: $$ \underbrace{\overbrace{s_0 ~ s_1 ~ s_2}^{\pi[i]} ~ \overbrace{s_3}^{s_3 = s_{i+1}}}_{\pi[i+1] = \pi[i] + 1} ~ \dots ~ \underbrace{\overbrace{s_{i-2} ~ s_{i-1} ~ s_{i}}^{\pi[i]} ~ \overbrace{s_{i+1}}^{s_3 = s_i + 1}}_{\pi[i+1] = \pi[i] + 1} $$ -如果不是上述情况,即 $s[i + 1] \neq s[\pi[i]]$,那么我们需要尝试更短的字符串。为了加速,我们希望直接移动到最长的长度 $j < \pi[i]$,使得在位置 $i$ 的前缀性质仍得以保持,也即 $s[0 \dots j - 1] = s[i - j + 1 \dots i]$: +如果不是上述情况,即 $s[i + 1] \neq s[\pi[i]]$ ,那么我们需要尝试更短的字符串。为了加速,我们希望直接移动到最长的长度 $j < \pi[i]$ ,使得在位置 $i$ 的前缀性质仍得以保持,也即 $s[0 \dots j - 1] = s[i - j + 1 \dots i]$ : $$ \overbrace{\underbrace{s_0 ~ s_1}_j ~ s_2 ~ s_3}^{\pi[i]} ~ \dots ~ \overbrace{s_{i-3} ~ s_{i-2} ~ \underbrace{s_{i-1} ~ s_{i}}_j}^{\pi[i]} ~ s_{i+1} $$ -实际上,如果我们找到了这样的长度 $j$,那么我们仅需要再次比较 $s[i + 1]$ 和 $s[j]$。如果他们相等,那么我们置 $\pi[i + 1] = j + 1$。否则,我们需要找到小于 $j$ 的最大值使得前缀性质得以保持,如此反复。这个过程会一直持续,直到 $j = 0$。如果 $s[i + 1] = s[0]$,那么我们置 $\pi[i + 1] = 1$,否则 $\pi[i + 1] = 0$。 +实际上,如果我们找到了这样的长度 $j$ ,那么我们仅需要再次比较 $s[i + 1]$ 和 $s[j]$ 。如果他们相等,那么我们置 $\pi[i + 1] = j + 1$ 。否则,我们需要找到小于 $j$ 的最大值使得前缀性质得以保持,如此反复。这个过程会一直持续,直到 $j = 0$ 。如果 $s[i + 1] = s[0]$ ,那么我们置 $\pi[i + 1] = 1$ ,否则 $\pi[i + 1] = 0$ 。 -所以我们已经有了这个算法的一个大概雏形。现在仅剩的问题是对于 $j$,如何快速找到这样的长度。让我们重新叙述一遍:对于当前在位置 $i$ 使得前缀性质得以保持的长度 $j$,也即 $s[0 \dots j - 1] = s[i - j + 1 \dots i]$,我们希望找到最大的 $k < j$,使得前缀性质仍得以保持。 +所以我们已经有了这个算法的一个大概雏形。现在仅剩的问题是对于 $j$ ,如何快速找到这样的长度。让我们重新叙述一遍:对于当前在位置 $i$ 使得前缀性质得以保持的长度 $j$ ,也即 $s[0 \dots j - 1] = s[i - j + 1 \dots i]$ ,我们希望找到最大的 $k < j$ ,使得前缀性质仍得以保持。 $$ \overbrace{\underbrace{s_0 ~ s_1}_k ~ s_2 ~ s_3}^j ~ \dots ~ \overbrace{s_{i-3} ~ s_{i-2} ~ \underbrace{s_{i-1} ~ s_{i}}_k}^j ~s_{i+1} $$ -上图显示出 $k$ 必定为 $\pi[j - 1]$,而该值我们之前已经计算过了。 +上图显示出 $k$ 必定为 $\pi[j - 1]$ ,而该值我们之前已经计算过了。 ### 最终算法 @@ -77,10 +77,10 @@ $$ 以下是最终的流程: -- 在一个循环中以 $i = 1$ 到 $i = n - 1$ 的顺序计算前缀函数 $\pi[i]$ 的值($\pi[0]$ 被赋值为 $0$)。 -- 为了计算当前的前缀函数值 $\pi[i]$,我们令变量 $j$ 表示右端点位于 $i - 1$ 的最好的后缀的长度。初始时 $j = \pi[i - 1]$。 -- 通过比较 $s[j]$ 和 $s[i]$ 来检查长度为 $j + 1$ 的后缀是否同时也是一个前缀。如果二者相等,那么我们置 $\pi[i] = j + 1$,否则我们减少 $j$ 至 $\pi[j - 1]$ 并且重复该过程。 -- 如果 $j = 0$ 并且仍没有任何一次匹配,则置 $\pi[i] = 0$ 并移至下一个下标 $i + 1$。 +- 在一个循环中以 $i = 1$ 到 $i = n - 1$ 的顺序计算前缀函数 $\pi[i]$ 的值( $\pi[0]$ 被赋值为 $0$ )。 +- 为了计算当前的前缀函数值 $\pi[i]$ ,我们令变量 $j$ 表示右端点位于 $i - 1$ 的最好的后缀的长度。初始时 $j = \pi[i - 1]$ 。 +- 通过比较 $s[j]$ 和 $s[i]$ 来检查长度为 $j + 1$ 的后缀是否同时也是一个前缀。如果二者相等,那么我们置 $\pi[i] = j + 1$ ,否则我们减少 $j$ 至 $\pi[j - 1]$ 并且重复该过程。 +- 如果 $j = 0$ 并且仍没有任何一次匹配,则置 $\pi[i] = 0$ 并移至下一个下标 $i + 1$ 。 ### 实现 @@ -100,7 +100,7 @@ vector prefix_function(string s) { } ``` -这是一个**在线**算法,即其当数据到达时处理它——举例来说,你可以一个字符一个字符的读取字符串,立即处理它们以计算出每个字符的前缀函数值。该算法仍然需要存储字符串本身以及先前计算过的前缀函数值,但如果我们已经预先知道该字符串前缀函数的最大可能取值 $M$,那么我们仅需要存储该字符串的前 $M + 1$ 个字符以及对应的前缀函数值。 +这是一个**在线**算法,即其当数据到达时处理它——举例来说,你可以一个字符一个字符的读取字符串,立即处理它们以计算出每个字符的前缀函数值。该算法仍然需要存储字符串本身以及先前计算过的前缀函数值,但如果我们已经预先知道该字符串前缀函数的最大可能取值 $M$ ,那么我们仅需要存储该字符串的前 $M + 1$ 个字符以及对应的前缀函数值。 ## 应用 @@ -108,11 +108,11 @@ vector prefix_function(string s) { 该任务是前缀函数的一个典型应用。 -给定一个文本 $t$ 和一个字符串 $s$,我们尝试找到并展示 $s$ 在 $t$ 中的所有出现(occurrence)。 +给定一个文本 $t$ 和一个字符串 $s$ ,我们尝试找到并展示 $s$ 在 $t$ 中的所有出现(occurrence)。 为了简便起见,我们用 $n$ 表示字符串 $s$ 的长度,用 $m$ 表示文本 $t$ 的长度。 -我们构造一个字符串 $s + \# + t$,其中 $\#$ 为一个既不出现在 $s$ 中也不出现在 $t$ 中的分隔符。接下来计算该字符串的前缀函数。现在考虑该前缀函数除去最开始 $n + 1$ 个值(即属于字符串 $s$ 和分隔符的函数值)后其余函数值的意义。根据定义,$\pi[i]$ 为右端点在 $i$ 且同时为一个前缀的最长真子串的长度,具体到我们的这种情况下,其值为与 $s$ 的前缀相同且右端点位于 $i$ 的最长子串的长度。由于分隔符的存在,该长度不可能超过 $n$。而如果等式 $\pi[i] = n$ 成立,则意味着 $s$ 完整出现在该位置(即其右端点位于位置 $i$)。注意该位置的下标是对字符串 $s + \# + t$ 而言的。 +我们构造一个字符串 $s + \# + t$ ,其中 $\#$ 为一个既不出现在 $s$ 中也不出现在 $t$ 中的分隔符。接下来计算该字符串的前缀函数。现在考虑该前缀函数除去最开始 $n + 1$ 个值(即属于字符串 $s$ 和分隔符的函数值)后其余函数值的意义。根据定义, $\pi[i]$ 为右端点在 $i$ 且同时为一个前缀的最长真子串的长度,具体到我们的这种情况下,其值为与 $s$ 的前缀相同且右端点位于 $i$ 的最长子串的长度。由于分隔符的存在,该长度不可能超过 $n$ 。而如果等式 $\pi[i] = n$ 成立,则意味着 $s$ 完整出现在该位置(即其右端点位于位置 $i$ )。注意该位置的下标是对字符串 $s + \# + t$ 而言的。 因此如果在某一位置 $i$ 有 $\pi[i] = n$ 成立,则字符串 $s$ 在字符串 $t$ 的 $i - (n - 1) - (n + 1) = i - 2n$ 处出现。 @@ -122,9 +122,9 @@ vector prefix_function(string s) { ### 统计每个前缀的出现次数 -在该节我们将同时讨论两个问题。给定一个长度为 $n$ 的字符串 $s$,在问题的第一个变种中我们希望统计每个前缀 $s[0 \dots i]$ 在同一个字符串的出现次数,在问题的第二个变种中我们希望统计每个前缀 $s[0 \dots i]$ 在另一个给定字符串 $t$ 中的出现次数。 +在该节我们将同时讨论两个问题。给定一个长度为 $n$ 的字符串 $s$ ,在问题的第一个变种中我们希望统计每个前缀 $s[0 \dots i]$ 在同一个字符串的出现次数,在问题的第二个变种中我们希望统计每个前缀 $s[0 \dots i]$ 在另一个给定字符串 $t$ 中的出现次数。 -首先让我们来解决第一个问题。考虑位置 $i$ 的前缀函数值 $\pi[i]$。根据定义,其意味着字符串 $s$ 一个长度为 $\pi[i]$ 的前缀在位置 $i$ 出现并以 $i$ 为右端点,同时不存在一个更长的前缀满足前述定义。与此同时,更短的前缀可能以该位置为右端点。容易看出,我们遇到了在计算前缀函数时已经回答过的问题:给定一个长度为 $j$ 的前缀,同时其也是一个右端点位于 $i$ 的后缀,下一个更小的前缀长度 $k < j$ 是多少?该长度的前缀需同时也是一个右端点为 $i$ 的后缀。因此以位置 $i$ 为右端点,有长度为 $\pi[i]$ 的前缀,有长度为 $\pi[\pi[i] - 1]$ 的前缀,有长度为 $\pi[\pi[\pi[i] - 1] - 1]$ 的前缀,等等,直到长度变为 $0$。故而我们可以通过下述方式计算答案。 +首先让我们来解决第一个问题。考虑位置 $i$ 的前缀函数值 $\pi[i]$ 。根据定义,其意味着字符串 $s$ 一个长度为 $\pi[i]$ 的前缀在位置 $i$ 出现并以 $i$ 为右端点,同时不存在一个更长的前缀满足前述定义。与此同时,更短的前缀可能以该位置为右端点。容易看出,我们遇到了在计算前缀函数时已经回答过的问题:给定一个长度为 $j$ 的前缀,同时其也是一个右端点位于 $i$ 的后缀,下一个更小的前缀长度 $k < j$ 是多少?该长度的前缀需同时也是一个右端点为 $i$ 的后缀。因此以位置 $i$ 为右端点,有长度为 $\pi[i]$ 的前缀,有长度为 $\pi[\pi[i] - 1]$ 的前缀,有长度为 $\pi[\pi[\pi[i] - 1] - 1]$ 的前缀,等等,直到长度变为 $0$ 。故而我们可以通过下述方式计算答案。 ```c++ vector ans(n + 1); @@ -133,39 +133,39 @@ for (int i = n - 1; i > 0; i--) ans[pi[i - 1]] += ans[i]; for (int i = 0; i <= n; i++) ans[i]++; ``` -在上述代码中我们首先统计每个前缀函数值在数组 $\pi$ 中出现了多少次,然后再计算最后答案:如果我们知道长度为 $i$ 的前缀出现了恰好 $\text{ans}[i]$ 次,那么该值必须被叠加至其最长的既是后缀也是前缀的子串的出现次数中。在最后,为了统计原始的前缀,我们对每个结果加 $1$。 +在上述代码中我们首先统计每个前缀函数值在数组 $\pi$ 中出现了多少次,然后再计算最后答案:如果我们知道长度为 $i$ 的前缀出现了恰好 $\text{ans}[i]$ 次,那么该值必须被叠加至其最长的既是后缀也是前缀的子串的出现次数中。在最后,为了统计原始的前缀,我们对每个结果加 $1$ 。 -现在考虑第二个问题。我们应用来自 Knuth-Morris-Pratt 的技巧:构造一个字符串 $s + \# + t$ 并计算其前缀函数。与第一个问题唯一的不同之处在于,我们只关心与字符串 $t$ 相关的前缀函数值,即 $i \ge n + 1$ 的 $\pi[i]$。有了这些值之后,我们可以同样应用在第一个问题中的算法来解决该问题。 +现在考虑第二个问题。我们应用来自 Knuth-Morris-Pratt 的技巧:构造一个字符串 $s + \# + t$ 并计算其前缀函数。与第一个问题唯一的不同之处在于,我们只关心与字符串 $t$ 相关的前缀函数值,即 $i \ge n + 1$ 的 $\pi[i]$ 。有了这些值之后,我们可以同样应用在第一个问题中的算法来解决该问题。 ### 一个字符串中本质不同子串的数目 -给定一个长度为 $n$ 的字符串 $s$,我们希望计算其本质不同子串的数目。 +给定一个长度为 $n$ 的字符串 $s$ ,我们希望计算其本质不同子串的数目。 我们将迭代的解决该问题。换句话说,在知道了当前的本质不同子串的数目的情况下,我们要找出一种在 $s$ 末尾添加一个字符后重新计算该数目的方法。 -令 $k$ 为当前 $s$ 的本质不同子串数量。我们添加一个新的字符 $c$ 至 $s$。显然,会有一些新的子串以字符 $c$ 结尾。我们希望对这些以该字符结尾且我们之前未曾遇到的子串计数。 +令 $k$ 为当前 $s$ 的本质不同子串数量。我们添加一个新的字符 $c$ 至 $s$ 。显然,会有一些新的子串以字符 $c$ 结尾。我们希望对这些以该字符结尾且我们之前未曾遇到的子串计数。 -构造字符串 $t = s + c$ 并将其反转得到字符串 $t^{\sim}$。现在我们的任务变为计算有多少 $t^{\sim}$ 的前缀未在 $t^{\sim}$ 的其余任何地方出现。如果我们计算了 $t^{\sim}$ 的前缀函数最大值 $\pi_{\max}$,那么最长的出现在 $s$ 中的前缀其长度为 $\pi_{\max}$。自然的,所有更短的前缀也出现了。 +构造字符串 $t = s + c$ 并将其反转得到字符串 $t^{\sim}$ 。现在我们的任务变为计算有多少 $t^{\sim}$ 的前缀未在 $t^{\sim}$ 的其余任何地方出现。如果我们计算了 $t^{\sim}$ 的前缀函数最大值 $\pi_{\max}$ ,那么最长的出现在 $s$ 中的前缀其长度为 $\pi_{\max}$ 。自然的,所有更短的前缀也出现了。 -因此,当添加了一个新字符后新出现的子串数目为 $|s| + 1 - \pi_{\max}$。 +因此,当添加了一个新字符后新出现的子串数目为 $|s| + 1 - \pi_{\max}$ 。 -所以对于每个添加的字符,我们可以在 $O(n)$ 的时间内计算新子串的数目,故最终复杂度为 $O(n^2)$。 +所以对于每个添加的字符,我们可以在 $O(n)$ 的时间内计算新子串的数目,故最终复杂度为 $O(n^2)$ 。 值得注意的是,我们也可以重新计算在头部添加一个字符,或者从尾或者头移除一个字符时的本质不同子串数目。 ### 字符串压缩 -给定一个长度为 $n$ 的字符串 $s$,我们希望找到其最短的 “压缩” 表示,也即我们希望寻找一个最短的字符串 $t$,使得 $s$ 可以被 $t$ 的一份或多份拷贝的拼接表示。 +给定一个长度为 $n$ 的字符串 $s$ ,我们希望找到其最短的“压缩”表示,也即我们希望寻找一个最短的字符串 $t$ ,使得 $s$ 可以被 $t$ 的一份或多份拷贝的拼接表示。 显然,我们只需要找到 $t$ 的长度即可。知道了该长度,该问题的答案即为长度为该值的 $s$ 的前缀。 -让我们计算 $s$ 的前缀函数。通过使用该函数的最后一个值 $\pi[n - 1]$,我们定义值 $k = n - \pi[n - 1]$。我们将证明,如果 $k$ 整除 $n$,那么 $k$ 就是答案,否则不存在一个有效的压缩,故答案为 $n$。 +让我们计算 $s$ 的前缀函数。通过使用该函数的最后一个值 $\pi[n - 1]$ ,我们定义值 $k = n - \pi[n - 1]$ 。我们将证明,如果 $k$ 整除 $n$ ,那么 $k$ 就是答案,否则不存在一个有效的压缩,故答案为 $n$ 。 -假定 $n$ 可被 $k$ 整除。那么字符串可被划分为长度为 $k$ 的若干块。根据前缀函数的定义,该字符串长度为 $n - k$ 的前缀等于其后缀。但是这意味着最后一个块同倒数第二个块相等,并且倒数第二个块同倒数第三个块相等,等等。作为其结果,所有块都是相等的,因此我们可以将字符串 $s$ 压缩至长度 $k$。 +假定 $n$ 可被 $k$ 整除。那么字符串可被划分为长度为 $k$ 的若干块。根据前缀函数的定义,该字符串长度为 $n - k$ 的前缀等于其后缀。但是这意味着最后一个块同倒数第二个块相等,并且倒数第二个块同倒数第三个块相等,等等。作为其结果,所有块都是相等的,因此我们可以将字符串 $s$ 压缩至长度 $k$ 。 诚然,我们仍需证明该值为最优解。实际上,如果有一个比 $k$ 更小的压缩表示,那么前缀函数的最后一个值 $\pi[n - 1]$ 必定比 $n - k$ 要大。因此 $k$ 就是答案。 -现在假设 $n$ 不可以被 $k$ 整除,我们将通过反证法证明这意味着答案为 $n$[^1]。假设其最小压缩表示 $r$ 的长度为 $p$($p$ 整除 $n$),字符串 $s$ 被划分为 $n / p \ge 2$ 块。那么前缀函数的最后一个值 $\pi[n - 1]$ 必定大于 $n - p$(如果等于则 $n$ 可被 $k$ 整除),也即其所表示的后缀将部分的覆盖第一个块。现在考虑字符串的第二个块。该块有两种解释:第一种为 $r_0 r_1 \dots r_{p - 1}$,另一种为 $r_{p - k} r_{p - k + 1} \dots r_{p - 1} r_0 r_1 \dots r_{p - k - 1}$ 。由于两种解释对应同一个字符串,因此可得到 $p$ 个方程组成的方程组,该方程组可简写为 $r_{(i + k) \bmod p} = r_{i \bmod p}$,其中 $\cdot \bmod p$ 表示模 $p$ 意义下的最小非负剩余。 +现在假设 $n$ 不可以被 $k$ 整除,我们将通过反证法证明这意味着答案为 $n$ [^1]。假设其最小压缩表示 $r$ 的长度为 $p$ ( $p$ 整除 $n$ ),字符串 $s$ 被划分为 $n / p \ge 2$ 块。那么前缀函数的最后一个值 $\pi[n - 1]$ 必定大于 $n - p$ (如果等于则 $n$ 可被 $k$ 整除),也即其所表示的后缀将部分的覆盖第一个块。现在考虑字符串的第二个块。该块有两种解释:第一种为 $r_0 r_1 \dots r_{p - 1}$ ,另一种为 $r_{p - k} r_{p - k + 1} \dots r_{p - 1} r_0 r_1 \dots r_{p - k - 1}$ 。由于两种解释对应同一个字符串,因此可得到 $p$ 个方程组成的方程组,该方程组可简写为 $r_{(i + k) \bmod p} = r_{i \bmod p}$ ,其中 $\cdot \bmod p$ 表示模 $p$ 意义下的最小非负剩余。 $$ \begin{gathered} @@ -174,17 +174,17 @@ r_0 ~ r_1 ~ r_2 ~ r_3 ~ \underbrace{\overbrace{r_0 ~ r_1 ~ r_2 ~ r_3 ~ r_4 ~ r_5 \end{gathered} $$ -根据扩展欧几里得算法我们可以得到一组 $x$ 和 $y$ 使得 $xk + yp = \gcd(k, p)$。通过与等式 $pk - kp = 0$ 适当叠加我们可以得到一组 $x' > 0$ 和 $y' < 0$ 使得 $x'k + y'p = \gcd(k, p)$。这意味着通过不断应用前述方程组中的方程我们可以得到新的方程组 $r_{(i + \gcd(k, p)) \bmod p} = r_{i \bmod p}$。 +根据扩展欧几里得算法我们可以得到一组 $x$ 和 $y$ 使得 $xk + yp = \gcd(k, p)$ 。通过与等式 $pk - kp = 0$ 适当叠加我们可以得到一组 $x' > 0$ 和 $y' < 0$ 使得 $x'k + y'p = \gcd(k, p)$ 。这意味着通过不断应用前述方程组中的方程我们可以得到新的方程组 $r_{(i + \gcd(k, p)) \bmod p} = r_{i \bmod p}$ 。 -由于 $\gcd(k, p)$ 整除 $p$,这意味着 $\gcd(k, p)$ 是 $r$ 的一个周期。又因为 $\pi[n - 1] > n - p$,故有 $n - \pi[n - 1] = k < p$,所以 $\gcd(k, p)$ 是一个比 $p$ 更小的 $r$ 的周期。因此字符串 $s$ 有一个长度为 $\gcd(k, p) < p$ 的压缩表示,同 $p$ 的最小性矛盾。 +由于 $\gcd(k, p)$ 整除 $p$ ,这意味着 $\gcd(k, p)$ 是 $r$ 的一个周期。又因为 $\pi[n - 1] > n - p$ ,故有 $n - \pi[n - 1] = k < p$ ,所以 $\gcd(k, p)$ 是一个比 $p$ 更小的 $r$ 的周期。因此字符串 $s$ 有一个长度为 $\gcd(k, p) < p$ 的压缩表示,同 $p$ 的最小性矛盾。 -综上所述,不存在一个长度小于 $n$ 的压缩表示,因此答案为 $n$。 +综上所述,不存在一个长度小于 $n$ 的压缩表示,因此答案为 $n$ 。 [^1]: 在俄文版及英文版中该部分证明均疑似有误。本文章中的该部分证明由作者自行添加。 ### 根据前缀函数构建一个自动机 -让我们重新回到通过一个分隔符将两个字符串拼接的新字符串。对于字符串 $s$ 和 $t$ 我们计算 $s + \# + t$ 的前缀函数。显然,因为 $\#$ 是一个分隔符,前缀函数值永远不会超过 $|s|$。因此我们只需要存储字符串 $s + \#$ 和其对应的前缀函数值,之后就可以动态计算对于之后所有字符的前缀函数值: +让我们重新回到通过一个分隔符将两个字符串拼接的新字符串。对于字符串 $s$ 和 $t$ 我们计算 $s + \# + t$ 的前缀函数。显然,因为 $\#$ 是一个分隔符,前缀函数值永远不会超过 $|s|$ 。因此我们只需要存储字符串 $s + \#$ 和其对应的前缀函数值,之后就可以动态计算对于之后所有字符的前缀函数值: $$ \underbrace{s_0 ~ s_1 ~ \dots ~ s_{n-1} ~ \#}_{\text{need to store}} ~ \underbrace{t_0 ~ t_1 ~ \dots ~ t_{m-1}}_{\text{do not need to store}} @@ -194,7 +194,7 @@ $$ 换句话说,我们可以构造一个**自动机**(一个有限状态机):其状态为当前的前缀函数值,而从一个状态到另一个状态的转移则由下一个字符确定。 -因此,即使没有字符串 $t$,我们同样可以应用构造转移表的算法构造一个转移表 $( \text { old } \pi , c ) \rightarrow \text { new } _ { - } \pi$: +因此,即使没有字符串 $t$ ,我们同样可以应用构造转移表的算法构造一个转移表 $( \text { old } \pi , c ) \rightarrow \text { new } _ { - } \pi$ : ```c++ void compute_automaton(string s, vector>& aut) { @@ -213,7 +213,7 @@ void compute_automaton(string s, vector>& aut) { } ``` -然而在这种形式下,对于小写字母表,算法的时间复杂度为 $O(n^2 26)$。注意到我们可以应用动态规划来利用表中已计算过的部分。只要我们从值 $j$ 变化到 $\pi[j - 1]$,那么我们实际上在说转移 $(j, c)$ 所到达的状态同转移 $(\pi[j - 1], c)$ 一样,但该答案我们之前已经精确计算过了。 +然而在这种形式下,对于小写字母表,算法的时间复杂度为 $O(n^2 26)$ 。注意到我们可以应用动态规划来利用表中已计算过的部分。只要我们从值 $j$ 变化到 $\pi[j - 1]$ ,那么我们实际上在说转移 $(j, c)$ 所到达的状态同转移 $(\pi[j - 1], c)$ 一样,但该答案我们之前已经精确计算过了。 ```c++ void compute_automaton(string s, vector>& aut) { @@ -242,7 +242,7 @@ void compute_automaton(string s, vector>& aut) { 但除此以外,还有第二个不那么直接的应用。我们可以在字符串 $t$ 是**某些通过一些规则构造的巨型字符串**时,使用该自动机加速计算。Gray 字符串,或者一个由一些短的输入串的递归组合所构造的字符串都是这种例子。 -出于完整性考虑,我们来解决这样一个问题:给定一个数 $k \le 10^5$,以及一个长度 $\le 10^5$ 的字符串 $s$,我们需要计算 $s$ 在第 $k$ 个 Gray 字符串中的出现次数。回想起 Gray 字符串以下述方式定义: +出于完整性考虑,我们来解决这样一个问题:给定一个数 $k \le 10^5$ ,以及一个长度 $\le 10^5$ 的字符串 $s$ ,我们需要计算 $s$ 在第 $k$ 个 Gray 字符串中的出现次数。回想起 Gray 字符串以下述方式定义: $$ \begin{aligned} @@ -255,9 +255,9 @@ $$ 由于其天文数字般的长度,在这种情况下即使构造字符串 $t$ 都是不可能的:第 $k$ 个 Gray 字符串有 $2^k - 1$ 个字符。然而我们可以在仅仅知道开头若干前缀函数值的情况下,有效计算该字符串末尾的前缀函数值。 -除了自动机之外,我们同时需要计算值 $G[i][j]$:在从状态 $j$ 开始处理 $g_i$ 后的自动机的状态,以及值 $K[i][j]$:当从状态 $j$ 开始处理 $g_i$ 后,$s$ 在 $g_i$ 中的出现次数。实际上 $K[i][j]$ 为在执行操作时前缀函数取值为 $|s|$ 的次数。易得问题的答案为 $K[k][0]$。 +除了自动机之外,我们同时需要计算值 $G[i][j]$ :在从状态 $j$ 开始处理 $g_i$ 后的自动机的状态,以及值 $K[i][j]$ :当从状态 $j$ 开始处理 $g_i$ 后, $s$ 在 $g_i$ 中的出现次数。实际上 $K[i][j]$ 为在执行操作时前缀函数取值为 $|s|$ 的次数。易得问题的答案为 $K[k][0]$ 。 -我们该如何计算这些值呢?首先根据定义,初始条件为 $G[0][j] = j$ 以及 $K[0][j] = 0$。之后所有值可以通过先前的值以及使用自动机计算得到。为了对某个 $i$ 计算相应值,回想起字符串 $g_i$ 由 $g_{i - 1}$,字母表中第 $i$ 个字符,以及 $g_{i - 1}$ 三者拼接而成。因此自动机会途径下列状态: +我们该如何计算这些值呢?首先根据定义,初始条件为 $G[0][j] = j$ 以及 $K[0][j] = 0$ 。之后所有值可以通过先前的值以及使用自动机计算得到。为了对某个 $i$ 计算相应值,回想起字符串 $g_i$ 由 $g_{i - 1}$ ,字母表中第 $i$ 个字符,以及 $g_{i - 1}$ 三者拼接而成。因此自动机会途径下列状态: $$ \begin{gathered} @@ -266,13 +266,13 @@ G[i][j] = G[i - 1][\text{mid}] \end{gathered} $$ -$K[i][j]$ 的值同样可被简单计算。 + $K[i][j]$ 的值同样可被简单计算。 $$ K[i][j] = K[i - 1][j] + [\text{mid} == |s|] + K[i - 1][\text{mid}] $$ -其中 $[\cdot]$ 当其中表达式取值为真时值为 $1$,否则为 $0$。综上,我们已经可以解决关于 Gray 字符串的问题,以及一大类与之类似的问题。举例来说,应用同样的方法可以解决下列问题:给定一个字符串 $s$ 以及一些模式 $t_i$,其中每个模式以下列方式给出:该模式由普通字符组成,当中可能以 $t_{k}^{\text{cnt}}$ 的形式递归插入先前的字符串,也即在该位置我们必须插入字符串 $t_k$ $\text{cnt}$ 次。以下是这些模式的一个例子: +其中 $[\cdot]$ 当其中表达式取值为真时值为 $1$ ,否则为 $0$ 。综上,我们已经可以解决关于 Gray 字符串的问题,以及一大类与之类似的问题。举例来说,应用同样的方法可以解决下列问题:给定一个字符串 $s$ 以及一些模式 $t_i$ ,其中每个模式以下列方式给出:该模式由普通字符组成,当中可能以 $t_{k}^{\text{cnt}}$ 的形式递归插入先前的字符串,也即在该位置我们必须插入字符串 $t_k$ $\text{cnt}$ 次。以下是这些模式的一个例子: $$ \begin{aligned} @@ -301,4 +301,4 @@ $$ * * * -**本页面主要译自博文 [Префикс-функция. Алгоритм Кнута-Морриса-Пратта](http://e-maxx.ru/algo/prefix_function) 与其英文翻译版 [Prefix function. Knuth–Morris–Pratt algorithm](https://cp-algorithms.com/string/prefix-function.html) 。其中俄文版版权协议为 Public Domain + Leave a Link;英文版版权协议为 CC-BY-SA 4.0。** +**本页面主要译自博文[Префикс-функция. Алгоритм Кнута-Морриса-Пратта](http://e-maxx.ru/algo/prefix_function)与其英文翻译版[Prefix function. Knuth–Morris–Pratt algorithm](https://cp-algorithms.com/string/prefix-function.html)。其中俄文版版权协议为 Public Domain + Leave a Link;英文版版权协议为 CC-BY-SA 4.0。** diff --git a/docs/string/sa.md b/docs/string/sa.md index 2942e2d4..63a75925 100644 --- a/docs/string/sa.md +++ b/docs/string/sa.md @@ -6,21 +6,21 @@ ### 各种定义 -`子串`: 就是子串 [捂脸] + `子串` :就是子串[捂脸] -`后缀`: 就是从 $i$ 这个位置开始到该字符串的末尾的一个子串 + `后缀` :就是从 $i$ 这个位置开始到该字符串的末尾的一个子串 -`字符串大小比较`: $a$ 和 $b$ 这两个串,从头开始逐个字符按照 ASCII 码进行比较。(按照字典序比较) + `字符串大小比较` : $a$ 和 $b$ 这两个串,从头开始逐个字符按照 ASCII 码进行比较。(按照字典序比较) -`后缀数组`: $sa[i]$ 代表该字符串的 $len$ 个后缀中,从 $sa[i]$ 开始的后缀排在为 $i$ 个。$sa$ 数组记录的是 “排第几的哪个后缀”。 + `后缀数组` : $sa[i]$ 代表该字符串的 $len$ 个后缀中,从 $sa[i]$ 开始的后缀排在为 $i$ 个。 $sa$ 数组记录的是“排第几的哪个后缀”。 -`名次数组`: $rank[i]$ 代表从 $i$ 开始的后缀排名为 $rank[i]$。$rank$ 数组记录的是 “某个后缀排在第几个”。 + `名次数组` : $rank[i]$ 代表从 $i$ 开始的后缀排名为 $rank[i]$ 。 $rank$ 数组记录的是“某个后缀排在第几个”。 ## 一些构造方法 ### 最简单的暴力 -把所有的后缀拆出来,然后 sort。由于直接比较长度为 n 的字符串的时间复杂度为 $O(n)$,所以整体时间复杂度为 $O(n^2 \log n)$ +把所有的后缀拆出来,然后 sort。由于直接比较长度为 n 的字符串的时间复杂度为 $O(n)$ ,所以整体时间复杂度为 $O(n^2 \log n)$ ```cpp int rank[123], sa[123]; @@ -57,9 +57,9 @@ int main() { 复杂度是 $O(nlogn)$ -前提是你要先会 ** 基数排序 ** +前提是你要先会**基数排序** -假设我们有这样一个字符串 `aabaaaab` +假设我们有这样一个字符串 `aabaaaab` 然后我们把所有的后缀列举出来: @@ -75,13 +75,13 @@ int main() { ![](images/sa3.png) -第三字母同理........ +第三字母同理…… 这样子我们可以处理这个问题,可是复杂度还是没有到达一个我们可以接受的范围 -所以我们引入 ** 倍增 ** +所以我们引入**倍增** -当我们按照每个后缀的前 $2^k$ 个字母进行完排序后,那么我们把后缀的前 $2^{k+1}$ 看做前后两个 $2^k$, 这样我们就可以把这前后两个 $2^k$ 作为之前说的 `首字母` 和 `第二个字母` 了,然后进行上述过程,就可以在 $O(nlogn)$ 的复杂度内处理这个问题了 +当我们按照每个后缀的前 $2^k$ 个字母进行完排序后,那么我们把后缀的前 $2^{k+1}$ 看做前后两个 $2^k$ , 这样我们就可以把这前后两个 $2^k$ 作为之前说的 `首字母` 和 `第二个字母` 了,然后进行上述过程,就可以在 $O(nlogn)$ 的复杂度内处理这个问题了 ```cpp #include @@ -134,7 +134,7 @@ int main() { 代码里 $x[i]$ 就是 $rank[i]$ -$y[i]$ :假设 $y[i]=a\ ,\ y[i+1]=b$ 那么在原串中 从 $a+2^k$ 开始的 $2^k$ 个字符组成的子串 ** 小于等于 ** 从 $b+2^k$ 开始的 $2^k$ 个字符组成的子串 + $y[i]$ :假设 $y[i]=a\ ,\ y[i+1]=b$ 那么在原串中 从 $a+2^k$ 开始的 $2^k$ 个字符组成的子串**小于等于**从 $b+2^k$ 开始的 $2^k$ 个字符组成的子串 最好理解这个代码时,每一步都结合这基数排序来考虑 diff --git a/docs/string/sam.md b/docs/string/sam.md index 945156c4..a13eaa19 100644 --- a/docs/string/sam.md +++ b/docs/string/sam.md @@ -7,11 +7,11 @@ 以上问题都可以在线性的时间复杂度内通过后缀自动机来实现。 -直观上,字符串的后缀自动机可以理解为给定字符串的**所有子串**的压缩形式。值得注意的事实是,后缀自动机将所有的这些信息以高度压缩的形式储存。对于一个长度为 $n$ 的字符串,它的空间复杂度仅为 $O(n)$。此外,构造后缀自动机的时间复杂度仅为 $O(n)$(这里我们将字符集的大小 $k$ 看作常数,否则时间复杂度和空间复杂度均为 $O(n\log k)$)。 +直观上,字符串的后缀自动机可以理解为给定字符串的**所有子串**的压缩形式。值得注意的事实是,后缀自动机将所有的这些信息以高度压缩的形式储存。对于一个长度为 $n$ 的字符串,它的空间复杂度仅为 $O(n)$ 。此外,构造后缀自动机的时间复杂度仅为 $O(n)$ (这里我们将字符集的大小 $k$ 看作常数,否则时间复杂度和空间复杂度均为 $O(n\log k)$ )。 ## 后缀自动机的定义 -给定字符串 $s$ 的后缀自动机是一个接受所有字符串 $s$ 的后缀的最小 **DFA**(确定性有限自动机或确定性有限状态自动机)。 +给定字符串 $s$ 的后缀自动机是一个接受所有字符串 $s$ 的后缀的最小**DFA**(确定性有限自动机或确定性有限状态自动机)。 换句话说: @@ -35,27 +35,27 @@ 我们用蓝色表示初始状态,用绿色表示终止状态。 -对于字符串 $s=``"$: +对于字符串 $s=``"$ : ![](./images/SAM/SA.svg) -对于字符串 $s=``a\!"$: +对于字符串 $s=``a\!"$ : ![](./images/SAM/SAa.svg) -对于字符串 $s=``aa\!"$: +对于字符串 $s=``aa\!"$ : ![](./images/SAM/SAaa.svg) -对于字符串 $s=``ab\!"$: +对于字符串 $s=``ab\!"$ : ![](./images/SAM/SAab.svg) -对于字符串 $s=``abb\!"$: +对于字符串 $s=``abb\!"$ : ![](./images/SAM/SAabb.svg) -对于字符串 $s=``abbb\!"$: +对于字符串 $s=``abbb\!"$ : ![](./images/SAM/SAabbb.svg) @@ -63,23 +63,23 @@ 在我们描述线性时间内构造后缀自动机的算法之前,我们需要引入几个对理解构造过程非常重要的新概念并简单证明。 -### 结束位置 +### 结束位置

考虑字符串 s 的任意非空子串 t ,我们记 endpos(t) 为在字符串 s 中 t 的所有结束位置(假设对字符串中字符的编号从零开始)。例如,对于字符串 ``abcbc\!",我们有 endpos(``bc\!")=2,\,4。

-当两个子串 $t_1$ 与 $t_2$ 的末尾集合相等时我们称它们是 $endpos$ 等价的:即 $endpos(t_1)=endpos(t_2)$。这样所有字符串 $s$ 的非空子串都可以根据它们的 **$endpos$** 集合被分为几个**等价类**。 +当两个子串 $t_1$ 与 $t_2$ 的末尾集合相等时我们称它们是 $endpos$ 等价的:即 $endpos(t_1)=endpos(t_2)$ 。这样所有字符串 $s$ 的非空子串都可以根据它们的** $endpos$ **集合被分为几个**等价类**。 -显然,在后缀自动机中的每个状态对应于一个或多个 $endpos$ 相同的子串。换句话说,后缀自动机中的状态数等于所有子串的等价类的个数,加上初始状态。后缀自动机的状态个数等价于 $endpos$ 相同的一个或多个子串所组成的集合的个数 $+1$。 +显然,在后缀自动机中的每个状态对应于一个或多个 $endpos$ 相同的子串。换句话说,后缀自动机中的状态数等于所有子串的等价类的个数,加上初始状态。后缀自动机的状态个数等价于 $endpos$ 相同的一个或多个子串所组成的集合的个数 $+1$ 。 我们稍后将会用这个假设介绍构造后缀自动机的算法。在那时我们将会发现,后缀自动机需要满足的所有性质,除了最小性以外都满足了。由 Nerode 定理我们可以得出最小性(这篇文章不会证明后缀自动机的最小性)。 由 $endpos$ 的值我们可以得到一些重要结论: -> **引理 1:**当且仅当字符串 $u$ 以 $w$ 的一个后缀的形式出现在字符串 $s$ 中时,两个非空子串 $u$ 和 $w$(假设 $length(u)\le length(w)$)是 $endpos$ 等价的。 +> **引理 1:**当且仅当字符串 $u$ 以 $w$ 的一个后缀的形式出现在字符串 $s$ 中时,两个非空子串 $u$ 和 $w$ (假设 $length(u)\le length(w)$ )是 $endpos$ 等价的。 引理显然成立。如果 $u$ 和 $v$ 的 $endpos$ 相同,则 $u$ 是 $w$ 的一个后缀,且只以 $s$ 中的一个 $w$ 的后缀的形式出现。且根据定义,如果 $u$ 为 $w$ 的一个后缀,且只以后缀的形式在 $s$ 中出现时,两个子串的 $endpos$ 值相等。 -> **引理 2:**考虑两个非空子串 $u$ 和 $w$(假设 $length(u)\le length(w)$)。则它们的 $endpos$ 构成的集合要么完全没有交集,要么 $endpos(w)$ 是 $endpos(u)$ 的一个子集。并且这依赖于 $u$ 是否为 $w$ 的一个后缀。即: +> **引理 2:**考虑两个非空子串 $u$ 和 $w$ (假设 $length(u)\le length(w)$ )。则它们的 $endpos$ 构成的集合要么完全没有交集,要么 $endpos(w)$ 是 $endpos(u)$ 的一个子集。并且这依赖于 $u$ 是否为 $w$ 的一个后缀。即: > > $$ > \begin{cases} @@ -90,15 +90,15 @@ 证明:如果集合 $endpos(u)$ 与 $endpos(w)$ 有至少一个公共元素,那么由于字符串 $u$ 与 $w$ 都在一个位置结束,即 $u$ 是 $w$ 的一个后缀。但是如果如此在每次 $w$ 出现的位置子串 $u$ 也会出现,这意味着 $endpos(w)$ 是 $endpos(u)$ 的一个子集。 -> **引理 3:**考虑一个 $endpos$ 等价类。将类中的所有子串按长度非递增的顺序排序。即每个子串都会比它前一个子串短,与此同时每个子串也是它前一个子串的一个后缀。换句话说,同一等价类中任两子串中较短者为较长者后缀,且该类中的子串长度恰好覆盖整个区间 $[x,\,y]$。 +> **引理 3:**考虑一个 $endpos$ 等价类。将类中的所有子串按长度非递增的顺序排序。即每个子串都会比它前一个子串短,与此同时每个子串也是它前一个子串的一个后缀。换句话说,同一等价类中任两子串中较短者为较长者后缀,且该类中的子串长度恰好覆盖整个区间 $[x,\,y]$ 。 证明:固定一些 $endpos$ 等价类。如果等价类中只包含一个子串,引理显然成立。现在我们来讨论子串元素个数大于 $1$ 的等价类。 由引理 1,两个不同的 $endpos$ 等价字符串中较短的一个总是较长的一个的真后缀。因此,等价类中不可能有两个等长的字符串。 -记 $w$ 为等价类中最长的字符串,类似地,记 $u$ 为等价类中最短的字符串。由引理 1,字符串 $u$ 是字符串 $w$ 的真后缀。现在考虑长度在区间 $[length(u),\,length(w)]$ 中的 $w$ 的任意后缀。容易看出,这个后缀也在同一等价类中。因为这个后缀只能在字符串 $s$ 中以 $w$ 的一个后缀的形式存在(也因为较短的后缀 $u$ 在 $s$ 中只以 $w$ 的后缀的形式存在)。因此,由引理 1,这个后缀与字符串 $w$ $endpos$ 等价。 +记 $w$ 为等价类中最长的字符串,类似地,记 $u$ 为等价类中最短的字符串。由引理 1,字符串 $u$ 是字符串 $w$ 的真后缀。现在考虑长度在区间 $[length(u),\,length(w)]$ 中的 $w$ 的任意后缀。容易看出,这个后缀也在同一等价类中。因为这个后缀只能在字符串 $s$ 中以 $w$ 的一个后缀的形式存在(也因为较短的后缀 $u$ 在 $s$ 中只以 $w$ 的后缀的形式存在)。因此,由引理 1,这个后缀与字符串 $w$ $endpos$ 等价。 -### 后缀链接 +### 后缀链接 考虑后缀自动机中满足 $v\ne t_0$ 的一些状态。我们已经知道,状态 $v$ 对应于具有相同 $endpos$ 的等价类。我们如果定义 $w$ 为这些字符串中最长的一个,则所有其它的字符串都是 $w$ 的后缀。 @@ -106,17 +106,17 @@ 换句话说,一个**后缀链接** $link(v)$ 连接到对应于 $w$ 的最长后缀的另一个 $endpos$ 等价类的状态。 -以下我们假设初始状态 $t_0$ 对应于它自己这个等价类(只包含一个空字符串),为了方便我们规定 $endpos(t)=\{-1,\,0,\,\ldots,\,length(s)-1\}$。 +以下我们假设初始状态 $t_0$ 对应于它自己这个等价类(只包含一个空字符串),为了方便我们规定 $endpos(t)=\{-1,\,0,\,\ldots,\,length(s)-1\}$ 。 > **引理 4:**所有后缀链接构成一棵根节点为 $t_0$ 的树。 -证明:考虑任意满足 $v\ne t_0$ 的状态,一个后缀链接 $link(v)$ 连接到的状态对应于严格更短的字符串(根据后缀链接的定义和引理 3)。因此,通过在后缀链接上移动,我们早晚会到达对应空串的初始状态 $t_0$。 +证明:考虑任意满足 $v\ne t_0$ 的状态,一个后缀链接 $link(v)$ 连接到的状态对应于严格更短的字符串(根据后缀链接的定义和引理 3)。因此,通过在后缀链接上移动,我们早晚会到达对应空串的初始状态 $t_0$ 。 > **引理 5:**如果我们使用集合 $endpos$ 构造一棵树(所有子节点的集合为父节点的子集),则这个结构由后缀链接连接起来。 证明:由引理 2,我们可以用 $endpos$ 集合构造一棵树(因为两个集合要么完全没有交集要么互为子集)。 -我们现在考虑任意满足 $v\ne t_0$ 的状态和它的后缀链接 $link(v)$,由后缀链接和引理 2,我们可以得到 +我们现在考虑任意满足 $v\ne t_0$ 的状态和它的后缀链接 $link(v)$ ,由后缀链接和引理 2,我们可以得到 $$ endpos(v)\subseteq endpos(link(v)) @@ -132,9 +132,9 @@ $$ 在学习算法本身前,我们对之前学过的知识进行一下总结,并引入一些辅助记号。 -- $s$ 的子串可以根据它们结束的位置 $endpos$ 被划分为多个等价类; +- $s$ 的子串可以根据它们结束的位置 $endpos$ 被划分为多个等价类; - 后缀自动机由初始状态 $t_0$ 和与每一个 $endpos$ 等价类对应的每个状态组成; -- 对于每一个状态 $v$,一个或多个子串与之匹配。我们记 $longest(v)$ 为其中最长的一个字符串,记 $len(v)$ 为它的长度。类似地,记 $shortest(v)$ 为最短的子串,它的长度为 $minlen(v)$。那么所有对应这个状态的所有字符串都是字符串 $longest(v)$ 的不同的后缀,且所有字符串的长度恰好覆盖区间 $[minlength(v),\,len(v)]$ 中的每一个整数。 +- 对于每一个状态 $v$ ,一个或多个子串与之匹配。我们记 $longest(v)$ 为其中最长的一个字符串,记 $len(v)$ 为它的长度。类似地,记 $shortest(v)$ 为最短的子串,它的长度为 $minlen(v)$ 。那么所有对应这个状态的所有字符串都是字符串 $longest(v)$ 的不同的后缀,且所有字符串的长度恰好覆盖区间 $[minlength(v),\,len(v)]$ 中的每一个整数。 - 对于任意满足 $v\ne t_0$ 的状态,定义后缀链接为连接到对应字符串 $longest(v)$ 的长度为 $minlen(v)-1$ 的后缀的一条边。从根节点 $t_0$ 出发的后缀链接可以形成一棵树,与此同时,这棵树形成了 $endpos$ 集合间的包含关系。 - 我们可以对 $v\ne t_0$ 的状态使用后缀链接 $link(v)$ 解释 $minlen(v)$ 如下: @@ -142,7 +142,7 @@ $$ minlen(v)=len(link(v))+1. $$ -- 如果我们从任意状态 $v_0$ 开始顺着后缀链接遍历,早晚都会到达初始状态 $t_0$。这种情况下我们可以得到一个互不相交的区间 $[minlen(v_i),\,len(v_i)]$ 的序列,且它们的并集形成了连续的区间 $[0,\,len(v_0)]$。 +- 如果我们从任意状态 $v_0$ 开始顺着后缀链接遍历,早晚都会到达初始状态 $t_0$ 。这种情况下我们可以得到一个互不相交的区间 $[minlen(v_i),\,len(v_i)]$ 的序列,且它们的并集形成了连续的区间 $[0,\,len(v_0)]$ 。 ### 算法 @@ -150,21 +150,21 @@ $$ 为了保证线性的空间复杂度,我们将只保存 $len$ 和 $link$ 的值和每个状态的一个转移列表,我们不会标记终止状态(但是我们稍后会展示在构造后缀自动机后如何分配这些标记)。 -一开始后缀自动机只包含一个状态 $t_0$,编号为 $0$(其它状态的编号为 $1,\,2,\,\ldots$)。为了方便,我们分配给它 $len=0$ 和 $link=-1$($-1$ 表示一个虚拟的不存在的状态)。 +一开始后缀自动机只包含一个状态 $t_0$ ,编号为 $0$ (其它状态的编号为 $1,\,2,\,\ldots$ )。为了方便,我们分配给它 $len=0$ 和 $link=-1$ ( $-1$ 表示一个虚拟的不存在的状态)。 现在整个任务转化为实现给当前字符串添加一个字符 $c$ 的过程。算法流程如下: -- 令 $last$ 为对应添加字符 $c$ 之前的整个字符串(一开始我们设置 $last=0$ 且我们会在算法的最后一步对应地更新 $last$)。 -- 创建一个新的状态 $cur$,并将 $len(cur)$ 赋值为 $len(last)+1$,在这时 $link(cur)$ 的值还未知。 -- 现在我们按以下流程进行:我们从状态 $last$ 开始。如果还没有到字符 $c$ 的转移,我们就添加一个到状态 $cur$ 的转移,遍历后缀链接。如果在某个点已经存在到字符 $c$ 的转移,我们就停下来,并将这个状态标记为 $p$。 -- 如果没有找到这样的状态 $p$,我们就到达了虚拟状态 $-1$,我们将 $link(cur)$ 赋值为 $0$ 并退出。 -- 假设现在我们找到了一个状态 $p$,其可以转移到字符 $c$,我们将这个状态转移到的状态标记为 $q$。 -- 现在我们分类讨论两种状态,要么 $len(p) + 1 = len(q)$,要么不是。 -- 如果 $len(p)+1=len(q)$,我们只要将 $link(cur)$ 赋值为 $q$ 并退出。 -- 否则就会有些复杂。需要**复制**状态 $q$:我们创建一个新的状态 $clone$,复制 $q$ 的除了 $len$ 的值以外的所有信息(后缀链接和转移)。我们将 $len(clone)$ 赋值为 $len(p)+1$。 - 复制之后,我们将后缀链接从 $cur$ 指向 $clone$,也从 $q$ 指向 $clone$。 - 最终我们需要使用后缀链接从状态 $p$ 返回,因为存在一条通过 $c$ 到状态 $q$ 的转移,并在此过程中重定向所有状态到状态 $clone$。 -- 以上三种情况,在完成这个过程之后,我们将 $last$ 的值更新为状态 $cur$。 +- 令 $last$ 为对应添加字符 $c$ 之前的整个字符串(一开始我们设置 $last=0$ 且我们会在算法的最后一步对应地更新 $last$ )。 +- 创建一个新的状态 $cur$ ,并将 $len(cur)$ 赋值为 $len(last)+1$ ,在这时 $link(cur)$ 的值还未知。 +- 现在我们按以下流程进行:我们从状态 $last$ 开始。如果还没有到字符 $c$ 的转移,我们就添加一个到状态 $cur$ 的转移,遍历后缀链接。如果在某个点已经存在到字符 $c$ 的转移,我们就停下来,并将这个状态标记为 $p$ 。 +- 如果没有找到这样的状态 $p$ ,我们就到达了虚拟状态 $-1$ ,我们将 $link(cur)$ 赋值为 $0$ 并退出。 +- 假设现在我们找到了一个状态 $p$ ,其可以转移到字符 $c$ ,我们将这个状态转移到的状态标记为 $q$ 。 +- 现在我们分类讨论两种状态,要么 $len(p) + 1 = len(q)$ ,要么不是。 +- 如果 $len(p)+1=len(q)$ ,我们只要将 $link(cur)$ 赋值为 $q$ 并退出。 +- 否则就会有些复杂。需要**复制**状态 $q$ :我们创建一个新的状态 $clone$ ,复制 $q$ 的除了 $len$ 的值以外的所有信息(后缀链接和转移)。我们将 $len(clone)$ 赋值为 $len(p)+1$ 。 + 复制之后,我们将后缀链接从 $cur$ 指向 $clone$ ,也从 $q$ 指向 $clone$ 。 + 最终我们需要使用后缀链接从状态 $p$ 返回,因为存在一条通过 $c$ 到状态 $q$ 的转移,并在此过程中重定向所有状态到状态 $clone$ 。 +- 以上三种情况,在完成这个过程之后,我们将 $last$ 的值更新为状态 $cur$ 。 如果我们还想知道哪些状态是**终止状态**而哪些不是,我们可以在为字符串 $s$ 构造完完整的后缀自动机后找到所有的终止状态。为此,我们从对应整个字符串的状态(存储在变量 $last$ 中),遍历它的后缀链接,直到到达初始状态。我们将所有遍历到的节点都标记为终止节点。容易理解这样做我们会精确地标记字符串 $s$ 的所有后缀,这些状态恰好是终止状态。 @@ -176,23 +176,23 @@ $$ ### 正确性证明 -- 若一个转移 $(p,\,q)$ 满足 $len(p)+1=len(q)$ 则我们称这个转移是**连续的**。否则,即当 $len(p)+1len(p)+1$。这种情况下,我们必须要通过拆开状态 $q$ 来创建一个这样的状态。 -- 如果转移 $(p,\,q)$ 是连续的,那么 $len(q)=len(p)+1$。在这种情况下一切都很简单。我们只需要将 $cur$ 的后缀链接指向状态 $q$。 -- 否则转移是不连续的,即 $len(q)>len(p)+1$,这意味着状态 $q$ 不只对应于长度为$len(p)+1$ 的后缀 $s+c$,还对应于 $s$ 的更长的子串。除了将状态 $q$ 拆成两个子状态以外我们别无他法,所以第一个子状态的长度就是 $len(p)+1$ 了。 - 我们如何拆开一个状态呢?我们**复制**状态 $q$,产生一个状态 $clone$,我们将 $len(clone)$ 赋值为 $len(p)+1$。由于我们不想改变遍历到 $q$ 的路径,我们将 $q$ 的所有转移复制到 $clone$。我们也将从 $clone$ 出发的后缀链接设置为 $q$ 的后缀链接的目标,并设置 $q$ 的后缀链接为 $clone$。 - 在拆开状态后,我们将从 $cur$ 出发的后缀链接设置为 $clone$。 - 最后一步我们将一些到 $q$ 转移重定向到 $clone$。我们需要修改哪些转移呢?只重定向相当于所有字符串 $w+c$(其中 $w$ 是 $p$ 的最长字符串)的后缀就够了。即,我们需要继续沿着后缀链接遍历,从顶点 $p$ 直到虚拟状态 $-1$ 或者是转移到不是状态 $q$ 的一个转移。 +- 最简单的情况是我们到达了虚拟状态 $-1$ ,这意味着我们为所有 $s$ 的后缀添加了 $c$ 的转移。这也意味着,字符 $c$ 从未在字符串 $s$ 中出现过。因此 $cur$ 的后缀链接为状态 $0$ 。 +- 第二种情况下,我们找到了现有的转移 $(p,\,q)$ 。这意味着我们尝试向自动机内添加一个**已经存在的**字符串 $x+c$ (其中 $x$ 为 $s$ 的一个后缀,且字符串 $x+c$ 已经作为 $s$ 的一个子串出现过了)。因为我们假设字符串 $s$ 的自动机的构造是正确的,我们不应该在这里添加一个新的转移。然而,有一个难点。从状态 $cur$ 出发的后缀链接应该连接到哪个状态呢?我们要把后缀链接连到一个状态上,且其中最长的一个字符串恰好是 $x+c$ ,即这个状态的 $len$ 应该是 $len(p)+1$ 。然而还不存在这样的状态,即 $len(q)>len(p)+1$ 。这种情况下,我们必须要通过拆开状态 $q$ 来创建一个这样的状态。 +- 如果转移 $(p,\,q)$ 是连续的,那么 $len(q)=len(p)+1$ 。在这种情况下一切都很简单。我们只需要将 $cur$ 的后缀链接指向状态 $q$ 。 +- 否则转移是不连续的,即 $len(q)>len(p)+1$ ,这意味着状态 $q$ 不只对应于长度为 $len(p)+1$ 的后缀 $s+c$ ,还对应于 $s$ 的更长的子串。除了将状态 $q$ 拆成两个子状态以外我们别无他法,所以第一个子状态的长度就是 $len(p)+1$ 了。 + 我们如何拆开一个状态呢?我们**复制**状态 $q$ ,产生一个状态 $clone$ ,我们将 $len(clone)$ 赋值为 $len(p)+1$ 。由于我们不想改变遍历到 $q$ 的路径,我们将 $q$ 的所有转移复制到 $clone$ 。我们也将从 $clone$ 出发的后缀链接设置为 $q$ 的后缀链接的目标,并设置 $q$ 的后缀链接为 $clone$ 。 + 在拆开状态后,我们将从 $cur$ 出发的后缀链接设置为 $clone$ 。 + 最后一步我们将一些到 $q$ 转移重定向到 $clone$ 。我们需要修改哪些转移呢?只重定向相当于所有字符串 $w+c$ (其中 $w$ 是 $p$ 的最长字符串)的后缀就够了。即,我们需要继续沿着后缀链接遍历,从顶点 $p$ 直到虚拟状态 $-1$ 或者是转移到不是状态 $q$ 的一个转移。 ### 对操作次数为线性的证明 -首先我们假设字符集大小为**常数**。如果字符集大小不是常数,后缀自动机的时间复杂度就不是线性的。从一个顶点出发的转移存储在支持快速查询和插入的平衡树中。因此如果我们记 $k$ 为字符集的大小,则算法的渐进时间复杂度为 $O(n\log k)$,空间复杂度为 $O(n)$。然而如果字符集足够小,可以不写平衡树,以空间换时间将每个顶点的转移存储为长度为 $k$ 的数组(用于快速查询)和链表(用于快速遍历所有可用关键字)。这样算法的时间复杂度为 $O(n)$,空间复杂度为 $O(nk)$。 +首先我们假设字符集大小为**常数**。如果字符集大小不是常数,后缀自动机的时间复杂度就不是线性的。从一个顶点出发的转移存储在支持快速查询和插入的平衡树中。因此如果我们记 $k$ 为字符集的大小,则算法的渐进时间复杂度为 $O(n\log k)$ ,空间复杂度为 $O(n)$ 。然而如果字符集足够小,可以不写平衡树,以空间换时间将每个顶点的转移存储为长度为 $k$ 的数组(用于快速查询)和链表(用于快速遍历所有可用关键字)。这样算法的时间复杂度为 $O(n)$ ,空间复杂度为 $O(nk)$ 。 -所以我们将认为字符集的大小为常数,即每次对一个字符搜索转移、添加转移、查找下一个转移—这些操作的时间复杂度都为 $O(1)$。 +所以我们将认为字符集的大小为常数,即每次对一个字符搜索转移、添加转移、查找下一个转移—这些操作的时间复杂度都为 $O(1)$ 。 如果我们考虑算法的各个部分,算法中有三处时间复杂度不明显是线性的: @@ -204,7 +204,7 @@ $$ 因此上述**第一处和第二处**的总复杂度显然为线性的,因为单次操作均摊只为自动机添加了一个新转移。 -还需为**第三处**估计总复杂度,我们将最初指向 $q$ 的转移重定向到 $clone$。我们记 $v=longest(p)$,这是一个字符串 $s$ 的后缀,每次迭代长度都递减—因为作为字符串 $s$ 的位置随着每次迭代都单调上升。这种情况下,如果在循环的第一次迭代之前,相对应的字符串 $v$ 在距离 $last$ 的深度为 $k$ $(k\ge2)$ 的位置上(深度记为后缀链接的数量),那么在最后一次迭代后,字符串 $v+c$ 将会成为路径上第二个从 $cur$ 出发的后缀链接(它将会成为新的 $last$ 的值)。 +还需为**第三处**估计总复杂度,我们将最初指向 $q$ 的转移重定向到 $clone$ 。我们记 $v=longest(p)$ ,这是一个字符串 $s$ 的后缀,每次迭代长度都递减—因为作为字符串 $s$ 的位置随着每次迭代都单调上升。这种情况下,如果在循环的第一次迭代之前,相对应的字符串 $v$ 在距离 $last$ 的深度为 $k$ $(k\ge2)$ 的位置上(深度记为后缀链接的数量),那么在最后一次迭代后,字符串 $v+c$ 将会成为路径上第二个从 $cur$ 出发的后缀链接(它将会成为新的 $last$ 的值)。 因此,循环中的每次迭代都会使作为当前字符串的后缀的字符串 $longest(link(link(last))$ 的位置单调递增。因此这个循环最多不会执行超过 $n$ 次迭代,这正是我们需要证明的。 @@ -219,7 +219,7 @@ struct state { }; ``` -后缀自动机本身将会存储在一个 `state` 结构体数组中。我们记录当前自动机的大小 `sz` 和变量 `last`,当前整个字符串对应的状态。 +后缀自动机本身将会存储在一个 `state` 结构体数组中。我们记录当前自动机的大小 `sz` 和变量 `last` ,当前整个字符串对应的状态。 ```cpp const int MAXLEN = 100000; @@ -271,19 +271,19 @@ void sa_extend(char c) { } ``` -正如之前提到的一样,如果你用内存换时间(空间复杂度为 $O(nk)$,其中 $k$ 为字符集大小),你可以在 $O(n)$ 的时间内构造字符集大小 $k$ 任意的后缀自动机。但是这样你需要为每一个状态储存一个大小为 $k$ 的数组(用于快速跳转到转移的字符),和另外一个所有转移的链表(用于快速在转移中迭代)。 +正如之前提到的一样,如果你用内存换时间(空间复杂度为 $O(nk)$ ,其中 $k$ 为字符集大小),你可以在 $O(n)$ 的时间内构造字符集大小 $k$ 任意的后缀自动机。但是这样你需要为每一个状态储存一个大小为 $k$ 的数组(用于快速跳转到转移的字符),和另外一个所有转移的链表(用于快速在转移中迭代)。 ## 更多的性质 ### 状态数 -对于一个长度为 $n$ 的字符串 $s$,它的后缀自动机中的状态数**不会超过 $2n-1$** (假设 $n\ge2$)。 +对于一个长度为 $n$ 的字符串 $s$ ,它的后缀自动机中的状态数**不会超过 $2n-1$ **(假设 $n\ge2$ )。 对上述结论的证明就是算法本身,因为一开始自动机含有一个状态,第一次和第二次迭代中只会创建一个节点,剩余的 $n-2$ 步中每步会创建至多 $2$ 个状态。 -然而我们也能在**不知道这个算法**的情况下**展示**这个估计值。我们回忆一下状态数等于不同的 $endpos$ 集合个数。另外这些 $endpos$ 集合形成了一棵树(祖先节点的集合包含了它所有孩子节点的集合)。考虑将这棵树稍微变形一下:只要它有一个只有一个孩子的内部顶点(这意味着该子节点的集合至少遗漏了它的父集合中的一个位置),我们创建一个含有这个遗漏位置的集合。最后我们可以获得一棵每一个内部顶点的度数大于一的树,并且叶子节点的个数不超过 $n$。因此这样的树里有不超过 $2n-1$ 个节点。 +然而我们也能在**不知道这个算法**的情况下**展示**这个估计值。我们回忆一下状态数等于不同的 $endpos$ 集合个数。另外这些 $endpos$ 集合形成了一棵树(祖先节点的集合包含了它所有孩子节点的集合)。考虑将这棵树稍微变形一下:只要它有一个只有一个孩子的内部顶点(这意味着该子节点的集合至少遗漏了它的父集合中的一个位置),我们创建一个含有这个遗漏位置的集合。最后我们可以获得一棵每一个内部顶点的度数大于一的树,并且叶子节点的个数不超过 $n$ 。因此这样的树里有不超过 $2n-1$ 个节点。 -对于每个确定的 $n$,状态数的上界是确定的。一个可能的字符串是: +对于每个确定的 $n$ ,状态数的上界是确定的。一个可能的字符串是: $$ ``abbb\ldots bbb\!" @@ -293,15 +293,15 @@ $$ ### 转移数 -对于一个长度为 $n$ 的字符串 $s$,它的后缀自动机中的转移数**不会超过 $3n-4$**(假设 $n\ge 3$)。 +对于一个长度为 $n$ 的字符串 $s$ ,它的后缀自动机中的转移数**不会超过 $3n-4$ **(假设 $n\ge 3$ )。 证明如下: -我们首先估计连续的转移的数量。考虑自动机中从状态 $t_0$ 开始的最长路径的生成树。生成树的骨架只包含连续的边,因此数量少于状态数,即,边数不会超过 $2n-2$。 +我们首先估计连续的转移的数量。考虑自动机中从状态 $t_0$ 开始的最长路径的生成树。生成树的骨架只包含连续的边,因此数量少于状态数,即,边数不会超过 $2n-2$ 。 -现在我们来估计非连续的转移的数量。令当前非连续转移为 $(p,\,q)$,其字符为 $c$。我们取它的对应字符串 $u+c+w$,其中字符串 $u$ 对应于初始状态到 $p$ 的最长路径,$w$ 对应于从 $p$ 到任意终止状态的最长路径。一方面,对于每个不完整的字符串所对应的形如 $u+c+w$ 的字符串是不同的(因为字符串 $u$ 和 $w$ 仅由完整的转移组成)。另一方面,由终止状态的定义,每个形如 $u+c+w$ 的字符串都是整个字符串 $s$ 的后缀。因为 $s$ 只有 $n$ 个非空后缀,且形如 $u+c+w$ 的字符串都不包含 $s$(因为整个字符串只包含完整的转移),所以非完整的转移的总数不会超过 $n-1$。 +现在我们来估计非连续的转移的数量。令当前非连续转移为 $(p,\,q)$ ,其字符为 $c$ 。我们取它的对应字符串 $u+c+w$ ,其中字符串 $u$ 对应于初始状态到 $p$ 的最长路径, $w$ 对应于从 $p$ 到任意终止状态的最长路径。一方面,对于每个不完整的字符串所对应的形如 $u+c+w$ 的字符串是不同的(因为字符串 $u$ 和 $w$ 仅由完整的转移组成)。另一方面,由终止状态的定义,每个形如 $u+c+w$ 的字符串都是整个字符串 $s$ 的后缀。因为 $s$ 只有 $n$ 个非空后缀,且形如 $u+c+w$ 的字符串都不包含 $s$ (因为整个字符串只包含完整的转移),所以非完整的转移的总数不会超过 $n-1$ 。 -将以上两个估计值结合起来,我们可以得到上界 $3n-3$。然而,最大的状态数只能在测试数据 $``abbb\ldots bbb\!"$ 中产生,这个测试数据的转移数量显然少于 $3n-3$,我们可以获得更为紧确的后缀自动机的转移数的上界:$3n-4$。 +将以上两个估计值结合起来,我们可以得到上界 $3n-3$ 。然而,最大的状态数只能在测试数据 $``abbb\ldots bbb\!"$ 中产生,这个测试数据的转移数量显然少于 $3n-3$ ,我们可以获得更为紧确的后缀自动机的转移数的上界: $3n-4$ 。 上界可以通过字符串 @@ -317,15 +317,15 @@ $$ ### 检查字符串是否出现 -> 给一个文本串 $T$ 和多个模式串 $P$,我们要检查字符串 $P$ 是否作为 $T$ 的一个子串出现。 +> 给一个文本串 $T$ 和多个模式串 $P$ ,我们要检查字符串 $P$ 是否作为 $T$ 的一个子串出现。 -我们在 $O(length(T))$ 的时间内为文本串 $T$ 构造后缀自动机。为了检查模式串 $P$ 是否在 $T$ 中出现,我们沿转移(边)从 $t_0$ 开始根据 $P$ 的字符进行转移。如果在某个点无法转移下去,则模式串 $P$ 不是 $T$ 的一个子串。如果我们能够这样处理完整个字符串 $P$,那么模式串在 $T$ 中出现过。因此 +我们在 $O(length(T))$ 的时间内为文本串 $T$ 构造后缀自动机。为了检查模式串 $P$ 是否在 $T$ 中出现,我们沿转移(边)从 $t_0$ 开始根据 $P$ 的字符进行转移。如果在某个点无法转移下去,则模式串 $P$ 不是 $T$ 的一个子串。如果我们能够这样处理完整个字符串 $P$ ,那么模式串在 $T$ 中出现过。因此 -对于每个字符串 $P$ 算法的时间复杂度为 $O(length(P))$。此外,这个算法还找到了模式串 $P$ 在文本串中出现的最大前缀长度。 +对于每个字符串 $P$ 算法的时间复杂度为 $O(length(P))$ 。此外,这个算法还找到了模式串 $P$ 在文本串中出现的最大前缀长度。 ### 不同子串个数 -> 给一个字符串 $S$,计算不同子串的个数。 +> 给一个字符串 $S$ ,计算不同子串的个数。 为字符串 $S$ 构造后缀自动机。 @@ -339,57 +339,57 @@ $$ d[v]=1+\sum_{w:(v,\,w,\,c)\in DAWG}d[w] $$ -即,$d[v]$ 可以表示为所有 $v$ 的转移的末端的和。 +即, $d[v]$ 可以表示为所有 $v$ 的转移的末端的和。 -所以不同子串的个数为 $d[t_0]-1$(因为要去掉空子串)。 +所以不同子串的个数为 $d[t_0]-1$ (因为要去掉空子串)。 -总时间复杂度为:$O(length(S))$。 +总时间复杂度为: $O(length(S))$ 。 ### 所有不同子串的总长度 -> 给定一个字符串 $S$,计算所有不同子串的总长度。 +> 给定一个字符串 $S$ ,计算所有不同子串的总长度。 -本题做法与上一题类似,只是现在我们需要考虑分两部分进行动态规划:不同子串的数量 $d[v]$ 和它们的总长度 $ans[v]$。 +本题做法与上一题类似,只是现在我们需要考虑分两部分进行动态规划:不同子串的数量 $d[v]$ 和它们的总长度 $ans[v]$ 。 -我们已经在上一题中介绍了如何计算 $d[v]$。$ans[v]$ 的值可以使用通过以下递推式计算: +我们已经在上一题中介绍了如何计算 $d[v]$ 。 $ans[v]$ 的值可以使用通过以下递推式计算: $$ ans[v]=\sum_{w:(v,\,w,\,c)\in DAWG}d[w]+ans[w] $$ -我们取每个邻接顶点 $w$ 的答案,并加上 $d[w]$(因为从状态 $v$ 出发的子串都增加了一个字符)。 +我们取每个邻接顶点 $w$ 的答案,并加上 $d[w]$ (因为从状态 $v$ 出发的子串都增加了一个字符)。 -算法的时间复杂度仍然是 $O(length(S))$。 +算法的时间复杂度仍然是 $O(length(S))$ 。 -### 字典序第 大子串 +### 字典序第大子串 -> 给定一个字符串 $S$。多组询问,每组询问给定一个数 $K_i$,查询所有子串中字典序第 $k$ 大的子串。 +> 给定一个字符串 $S$ 。多组询问,每组询问给定一个数 $K_i$ ,查询所有子串中字典序第 $k$ 大的子串。 解决这个问题的思路基于前两个问题的思路。字典序第 $k$ 大的子串对应于后缀自动机中字典序第 $k$ 大的路径。因此在计算每个状态的路径数后,我们可以很容易地从后缀自动机的根开始找到第 $k$ 大的路径。 -预处理的时间复杂度为 $O(length(S))$,单次查询的复杂度为 $O(length(ans)\cdot k)$(其中 $ans$ 是查询的答案,$k$ 为字符集的大小)。 +预处理的时间复杂度为 $O(length(S))$ ,单次查询的复杂度为 $O(length(ans)\cdot k)$ (其中 $ans$ 是查询的答案, $k$ 为字符集的大小)。 ### 最小循环移位 -> 给定一个字符串 $S$。找出字典序最小的循环移位。 +> 给定一个字符串 $S$ 。找出字典序最小的循环移位。 我们为字符串 $S+S$ 构造后缀自动机。则后缀自动机本身将包含字符串 $S$ 的所有循环移位作为路径。 所以问题简化为寻找最小的长度为 $length(S)$ 的路径,这可以通过平凡的方法做到:我们从初始状态开始,贪心地访问最小的字符即可。 -总的时间复杂度为 $O(length(S))$。 +总的时间复杂度为 $O(length(S))$ 。 ### 出现次数 -> 对于一个给定的文本串 $T$,有多组询问,每组询问给一个模式串 $P$,回答模式串 $P$ 在字符串 $T$ 中作为子串出现了多少次。 +> 对于一个给定的文本串 $T$ ,有多组询问,每组询问给一个模式串 $P$ ,回答模式串 $P$ 在字符串 $T$ 中作为子串出现了多少次。 我们为文本串 $T$ 构造后缀自动机。 -接下来我们做以下的预处理:对于自动机中的每个状态 $v$,预处理值等于 $endpos(v)$ 这个集合大小的 $cnt[v]$。事实上对应于同一状态 $v$ 的所有子串在文本串 $T$ 中的出现次数相同,这相当于集合 $endpos$ 中的位置数。 +接下来我们做以下的预处理:对于自动机中的每个状态 $v$ ,预处理值等于 $endpos(v)$ 这个集合大小的 $cnt[v]$ 。事实上对应于同一状态 $v$ 的所有子串在文本串 $T$ 中的出现次数相同,这相当于集合 $endpos$ 中的位置数。 -然而我们不能明确的构造集合 $endpos$,因此我们只考虑它们的大小 $cnt$。 +然而我们不能明确的构造集合 $endpos$ ,因此我们只考虑它们的大小 $cnt$ 。 -为了计算这些值,我们进行以下操作。对于每个状态,如果它不是通过复制创建的(且它不是初始状态 $t_0$),我们用 $cnt=1$ 初始化它。然后我们按它们的长度 $len$ 降序遍历所有状态,并将当前的 $cnt[v]$ 的值加到后缀链接上,即: +为了计算这些值,我们进行以下操作。对于每个状态,如果它不是通过复制创建的(且它不是初始状态 $t_0$ ),我们用 $cnt=1$ 初始化它。然后我们按它们的长度 $len$ 降序遍历所有状态,并将当前的 $cnt[v]$ 的值加到后缀链接上,即: $$ cnt[link(v)]+=cnt[v] @@ -397,23 +397,23 @@ $$ 这样做每个状态的答案都是正确的。 -为什么这是正确的?不是通过复制获得的状态,恰好有 $length(T)$ 个,并且它们中的前 $i$ 个在我们插入前 $i$ 个字符时产生。因此对于每个这样的状态,我们在它被处理时计算它们所对应的位置的数量。因此我们初始将这些状态的 $cnt$ 的值赋为 $1$,其它状态的 $cnt$ 值赋为 $0$。 +为什么这是正确的?不是通过复制获得的状态,恰好有 $length(T)$ 个,并且它们中的前 $i$ 个在我们插入前 $i$ 个字符时产生。因此对于每个这样的状态,我们在它被处理时计算它们所对应的位置的数量。因此我们初始将这些状态的 $cnt$ 的值赋为 $1$ ,其它状态的 $cnt$ 值赋为 $0$ 。 -接下来我们对每一个 $v$ 执行以下操作:$cnt[link(v)]+=cnt[v]$。其背后的含义是,如果有一个字符串 $v$ 出现了 $cnt[v]$ 次,那么它的所有后缀也在完全相同的地方结束,即也出现了 $cnt[v]$ 次。 +接下来我们对每一个 $v$ 执行以下操作: $cnt[link(v)]+=cnt[v]$ 。其背后的含义是,如果有一个字符串 $v$ 出现了 $cnt[v]$ 次,那么它的所有后缀也在完全相同的地方结束,即也出现了 $cnt[v]$ 次。 为什么我们在这个过程中不会重复计数(即把某些位置数了两次)呢?因为我们只将一个状态的位置添加到**一个**其它的状态上,所以一个状态不可能以两种不同的方式将其位置重复地指向另一个状态。 因此,我们可以在 $O(length(T))$ 的时间内计算出所有状态的 $cnt$ 的值。 -最后回答询问只需要查找查找值 $cnt[t]$,其中 $t$ 为如果存在这样的状态就是状态对应的模式串,如果不存在答案就为 $0$。单次查询的时间复杂度为 $O(length(P))$。 +最后回答询问只需要查找查找值 $cnt[t]$ ,其中 $t$ 为如果存在这样的状态就是状态对应的模式串,如果不存在答案就为 $0$ 。单次查询的时间复杂度为 $O(length(P))$ 。 ### 第一次出现的位置 -> 给定一个文本串 $T$,多组查询。每次查询字符串 $P$ 在字符串 $T$ 中第一次出现的位置($P$ 的开头位置)。 +> 给定一个文本串 $T$ ,多组查询。每次查询字符串 $P$ 在字符串 $T$ 中第一次出现的位置( $P$ 的开头位置)。 -我们再构造一个后缀自动机。我们对自动机中的所有状态预处理位置 $firstpos$。即,对每个状态 $v$ 我们想要找到第一次出现这个状态的末端的位置 $firstpos[v]$。换句话说,我们希望先找到每个集合 $endpos$ 中的最小的元素(显然我们不能显式地维护所有 $endpos$ 集合)。 +我们再构造一个后缀自动机。我们对自动机中的所有状态预处理位置 $firstpos$ 。即,对每个状态 $v$ 我们想要找到第一次出现这个状态的末端的位置 $firstpos[v]$ 。换句话说,我们希望先找到每个集合 $endpos$ 中的最小的元素(显然我们不能显式地维护所有 $endpos$ 集合)。 -为了维护 $firstpos$ 这些位置,我们将原函数扩展为 `sa_extend()`。当我们创建新状态 $cur$ 时,我们令: +为了维护 $firstpos$ 这些位置,我们将原函数扩展为 `sa_extend()` 。当我们创建新状态 $cur$ 时,我们令: $$ firstpos(cur)=len(cur)-1 @@ -427,23 +427,23 @@ $$ (因为值的唯一其它选项 $firstpos(cur)$ 肯定太大了)。 -那么查询的答案就是 $firstpos(t)-length(P)+1$,其中 $t$ 为对应字符串 $P$ 的状态。单次查询只需要 $O(length(P))$ 的时间。 +那么查询的答案就是 $firstpos(t)-length(P)+1$ ,其中 $t$ 为对应字符串 $P$ 的状态。单次查询只需要 $O(length(P))$ 的时间。 ### 所有出现的位置 > 问题同上,这一次需要查询文本串 $T$ 中模式串出现的所有位置。 -我们还是为文本串 $T$ 构造后缀自动机。与上一个问题相似地,我们为所有状态计算位置 $firstpos$。 +我们还是为文本串 $T$ 构造后缀自动机。与上一个问题相似地,我们为所有状态计算位置 $firstpos$ 。 如果 $t$ 为对应于模式串 $T$ 的状态,显然 $firstpos(t)$ 为答案的一部分。需要查找的其它位置怎么办?我们使用了含有字符串 $P$ 的自动机,我们还需要将哪些状态纳入自动机呢?所有对应于以 $P$ 为后缀的字符串的状态。换句话说我们要找到所有可以通过后缀链接到达状态 $t$ 的状态。 因此为了解决这个问题,我们需要为每一个状态保存一个指向它的后缀引用列表。查询的答案就包含了对于每个我们能从状态 $t$ 只使用后缀引用进行 DFS 或 BFS 的所有状态的 $firstpos$ 值。 -这种变通方案的时间复杂度为 $O(answer(P))$,因为我们不会重复访问一个状态(因为对于仅有一个后缀链接指向一个状态,所以不存在两条不同的路径指向同一状态)。 +这种变通方案的时间复杂度为 $O(answer(P))$ ,因为我们不会重复访问一个状态(因为对于仅有一个后缀链接指向一个状态,所以不存在两条不同的路径指向同一状态)。 我们只需要考虑两个可能有相同 $endpos$ 值的不同状态。如果一个状态是由另一个复制而来的,则这种情况会发生。然而,这并不会对复杂度分析造成影响,因为每个状态至多被复制一次。 -此外,如果我们不从被复制的节点输出位置,我们也可以去除重复的位置。事实上对于一个状态,如果经过被复制状态可以到达,则经过原状态也可以到达。因此,如果我们给每个状态记录标记 `is_clone`,我们就可以简单地忽略掉被复制的状态,只输出其它所有状态的 $firstpos$ 的值。 +此外,如果我们不从被复制的节点输出位置,我们也可以去除重复的位置。事实上对于一个状态,如果经过被复制状态可以到达,则经过原状态也可以到达。因此,如果我们给每个状态记录标记 `is_clone` ,我们就可以简单地忽略掉被复制的状态,只输出其它所有状态的 $firstpos$ 的值。 以下是实现的框架: @@ -472,42 +472,42 @@ void output_all_occurrences(int v, int P_length) { 我们在字符串 $S$ 的后缀自动机上做动态规划。 -令 $d[v]$ 为节点 $v$ 的答案,即,我们已经处理完了子串的一部分,当前在状态 $v$,想找到不连续的转移需要添加的最小字符数量。计算 $d[v]$ 非常简单。如果不存在使用字符集中至少一个字符的转移,则 $d[v]=1$。否则添加一个字符是不够的,我们需要求出所有转移中的最小值: +令 $d[v]$ 为节点 $v$ 的答案,即,我们已经处理完了子串的一部分,当前在状态 $v$ ,想找到不连续的转移需要添加的最小字符数量。计算 $d[v]$ 非常简单。如果不存在使用字符集中至少一个字符的转移,则 $d[v]=1$ 。否则添加一个字符是不够的,我们需要求出所有转移中的最小值: $$ d[v]=1+\min_{w(v,\,w,\,c)\in SA}d[w] $$ -问题的答案就是 $d[t_0]$,字符串可以通过计算过的数组 $d[]$ 逆推回去。 +问题的答案就是 $d[t_0]$ ,字符串可以通过计算过的数组 $d[]$ 逆推回去。 ### 两个字符串的最长公共子串 -> 给定两个字符串 $S$ 和 $T$,求出最长公共子串,公共子串定义为在 $S$ 和 $T$ 中都作为子串出现过的字符串 $X$。 +> 给定两个字符串 $S$ 和 $T$ ,求出最长公共子串,公共子串定义为在 $S$ 和 $T$ 中都作为子串出现过的字符串 $X$ 。 我们为字符串 $S$ 构造后缀自动机。 -我们现在处理字符串 $T$,对于每一个前缀都在 $S$ 中寻找这个前缀的最长后缀。换句话说,对于每个字符串 $T$ 中的位置,我们想要找到这个位置结束的 $S$ 和 $T$ 的最长公共子串的长度。 +我们现在处理字符串 $T$ ,对于每一个前缀都在 $S$ 中寻找这个前缀的最长后缀。换句话说,对于每个字符串 $T$ 中的位置,我们想要找到这个位置结束的 $S$ 和 $T$ 的最长公共子串的长度。 -为了达到这一目的,我们使用两个变量,**当前状态** $v$ 和 **当前长度** $l$。这两个变量描述当前匹配的部分:它的长度和它们对应的状态。 +为了达到这一目的,我们使用两个变量,**当前状态** $v$ 和**当前长度** $l$ 。这两个变量描述当前匹配的部分:它的长度和它们对应的状态。 -一开始 $v=t_0$ 且 $l=0$,即,匹配为空串。 +一开始 $v=t_0$ 且 $l=0$ ,即,匹配为空串。 现在我们来描述如何添加一个字符 $T[i]$ 并为其重新计算答案: - 如果存在一个从 $v$ 到字符 $T[i]$ 的转移,我们只需要转移并让 $l$ 自增一。 -- 如果不存在这样的转移,我们需要缩短当前匹配的部分,这意味着我们需要按照以下后缀链接进行转移: +- 如果不存在这样的转移,我们需要缩短当前匹配的部分,这意味着我们需要按照以下后缀链接进行转移: $$ v=link(v) $$ - 与此同时,需要缩短当前长度。显然我们需要将 $l$ 赋值为 $len(v)$,因为经过这个后缀链接后我们到达的状态所对应的最长字符串是一个子串。 +与此同时,需要缩短当前长度。显然我们需要将 $l$ 赋值为 $len(v)$ ,因为经过这个后缀链接后我们到达的状态所对应的最长字符串是一个子串。 -- 如果仍然没有使用这一字符的转移,我们继续重复经过后缀链接并减小 $l$,直到我们找到一个转移或到达虚拟状态 $-1$(这意味着字符 $T[i]$ 根本没有在 $S$ 中出现过,所以我们设置 $v=l=0$)。 +- 如果仍然没有使用这一字符的转移,我们继续重复经过后缀链接并减小 $l$ ,直到我们找到一个转移或到达虚拟状态 $-1$ (这意味着字符 $T[i]$ 根本没有在 $S$ 中出现过,所以我们设置 $v=l=0$ )。 问题的答案就是所有 $l$ 的最大值。 -这一部分的时间复杂度为 $O(length(T))$,因为每次移动我们要么可以使 $l$ 增加一,要么可以在后缀链接间移动几次,每次都减小 $l$ 的值。 +这一部分的时间复杂度为 $O(length(T))$ ,因为每次移动我们要么可以使 $l$ 增加一,要么可以在后缀链接间移动几次,每次都减小 $l$ 的值。 代码实现: @@ -537,9 +537,9 @@ string lcs(string S, string T) { ### 多个字符串间的最长公共子串 -> 给定 $k$ 个字符串 $S_i$。我们需要找到它们的最长公共子串,即作为子串出现在每个字符串中的字符串 $X$。 +> 给定 $k$ 个字符串 $S_i$ 。我们需要找到它们的最长公共子串,即作为子串出现在每个字符串中的字符串 $X$ 。 -我们将所有的子串连接成一个较长的字符串 $T$,以特殊字符 $D_i$ 分开每个字符串(一个字符对应一个字符串): +我们将所有的子串连接成一个较长的字符串 $T$ ,以特殊字符 $D_i$ 分开每个字符串(一个字符对应一个字符串): $$ T=S_1+D_1+S_2+D_2+\cdots+S_k+D_k. @@ -554,26 +554,26 @@ $$ ## 例题 - SPOJ #7258 SUBLEX -- [HihoCoder #1441 : 后缀自动机一 · 基本概念](http://hihocoder.com/problemset/problem/1441) +- [HihoCoder #1441 : 后缀自动机一·基本概念](http://hihocoder.com/problemset/problem/1441) ## 相关资料 我们先给出与后缀自动机有关的最初的一些文献: -- A. Blumer, J. Blumer, A. Ehrenfeucht, D. Haussler, R. McConnell. **Linear +- A. Blumer, J. Blumer, A. Ehrenfeucht, D. Haussler, R. McConnell.**Linear Size Finite Automata for the Set of All Subwords of a Word. An Outline of - Results** [1983] -- A. Blumer, J. Blumer, A. Ehrenfeucht, D. Haussler. **The Smallest Automaton - Recognizing the Subwords of a Text** [1984] -- Maxime Crochemore. **Optimal Factor Transducers** [1985] -- Maxime Crochemore. **Transducers and Repetitions** [1986] -- A. Nerode. **Linear automaton transformations** [1958] + Results**[1983] +- A. Blumer, J. Blumer, A. Ehrenfeucht, D. Haussler.**The Smallest Automaton + Recognizing the Subwords of a Text**[1984] +- Maxime Crochemore.**Optimal Factor Transducers**[1985] +- Maxime Crochemore.**Transducers and Repetitions**[1986] +- A. Nerode.**Linear automaton transformations**[1958] 另外,在更新的一些资源里,在很多关于字符串算法的书中,都能找到这个主题: -- Maxime Crochemore, Rytter Wowjcieh. **Jewels of Stringology** [2002] -- Bill Smyth. **Computing Patterns in Strings** [2003] -- Bill Smith. **Methods and algorithms of calculations on lines** [2006] +- Maxime Crochemore, Rytter Wowjcieh.**Jewels of Stringology**[2002] +- Bill Smyth.**Computing Patterns in Strings**[2003] +- Bill Smith.**Methods and algorithms of calculations on lines**[2006] 另外,还有一些资料: @@ -586,5 +586,4 @@ $$ * * * -**本页面主要译自博文 -[Суффиксный автомат](http://e-maxx.ru/algo/suffix_automata) 与其英文翻译版 [Suffix Automaton](https://cp-algorithms.com/string/suffix-automaton.html) 。其中俄文版版权协议为 Public Domain + Leave a Link;英文版版权协议为 CC-BY-SA 4.0。** +**本页面主要译自博文[Суффиксный автомат](http://e-maxx.ru/algo/suffix_automata)与其英文翻译版[Suffix Automaton](https://cp-algorithms.com/string/suffix-automaton.html)。其中俄文版版权协议为 Public Domain + Leave a Link;英文版版权协议为 CC-BY-SA 4.0。** diff --git a/docs/string/trie.md b/docs/string/trie.md index 34621436..fec12342 100644 --- a/docs/string/trie.md +++ b/docs/string/trie.md @@ -12,10 +12,10 @@ 实际上要做的事情是求出 Trie 的每个节点的 $next$ 值 -当然,这里的 $next$ 不再是一个值,而是相当于是一个指针 —— 它可能指向其他分支的节点。 +当然,这里的 $next$ 不再是一个值,而是相当于是一个指针——它可能指向其他分支的节点。 这时 $next$ 的定义:最长的等于同长度的后缀的从根开始的路径的长度 -求法跟 [KMP](/string/kmp) 中的一样,只是要改成在 Trie 上 [BFS](/search/bfs) +求法跟[KMP](/string/kmp)中的一样,只是要改成在 Trie 上[BFS](/search/bfs) 复杂度:均摊分析失效了,其实只能在每条链上均摊分析,于是总复杂度为模式串长总和 diff --git a/docs/string/z-function.md b/docs/string/z-function.md index efe0c5dc..3e42d1cc 100644 --- a/docs/string/z-function.md +++ b/docs/string/z-function.md @@ -1,10 +1,10 @@ -假设我们有一个长度为 $n$ 的字符串 $s$。该字符串的**Z 函数**为一个长度为 $n$ 的数组,其中第 $i$ 个元素为满足从位置 $i$ 开始且为 $s$ 前缀的字符串的最大长度。 +假设我们有一个长度为 $n$ 的字符串 $s$ 。该字符串的**Z 函数**为一个长度为 $n$ 的数组,其中第 $i$ 个元素为满足从位置 $i$ 开始且为 $s$ 前缀的字符串的最大长度。 -换句话说,$z[i]$ 是 $s$ 和从 $i$ 开始的 $s$ 的后缀的最大公共前缀长度。 +换句话说, $z[i]$ 是 $s$ 和从 $i$ 开始的 $s$ 的后缀的最大公共前缀长度。 -**注意**:为了避免歧义,在这篇文章中下标从 $0$ 开始,即 $s$ 的第一个字符下标为 $0$,最后一个字符下标为 $n - 1$。 +**注意**:为了避免歧义,在这篇文章中下标从 $0$ 开始,即 $s$ 的第一个字符下标为 $0$ ,最后一个字符下标为 $n - 1$ 。 -Z 函数的第一个元素,$z[0]$,通常不是良定义的。在这篇文章中我们假定它是 $0$(虽然在算法实现中这没有任何影响)。 +Z 函数的第一个元素, $z[0]$ ,通常不是良定义的。在这篇文章中我们假定它是 $0$ (虽然在算法实现中这没有任何影响)。 国外一般将计算该数组的算法称为**Z Algorithm**,而国内则称其为**扩展 KMP**。 @@ -14,9 +14,9 @@ Z 函数的第一个元素,$z[0]$,通常不是良定义的。在这篇文章 下面若干样例展示了对于不同字符串的 Z 函数: -- $Z(\mathtt{aaaaa}) = [0, 4, 3, 2, 1]$ -- $Z(\mathtt{aaabaab}) = [0, 2, 1, 0, 2, 1, 0]$ -- $Z(\mathtt{abacaba}) = [0, 0, 1, 0, 3, 0, 1]$ +- $Z(\mathtt{aaaaa}) = [0, 4, 3, 2, 1]$ +- $Z(\mathtt{aaabaab}) = [0, 2, 1, 0, 2, 1, 0]$ +- $Z(\mathtt{abacaba}) = [0, 0, 1, 0, 3, 0, 1]$ ## 朴素算法 @@ -32,31 +32,31 @@ vector z_function_trivial(string s) { } ``` -我们做的仅仅为循环每个位置 $i$,并通过下述做法更新每个 $z[i]$:从 $z[i] = 0$ 开始,只要我们没有失配(并且没有到达末尾)就将其加 $1$。 +我们做的仅仅为循环每个位置 $i$ ,并通过下述做法更新每个 $z[i]$ :从 $z[i] = 0$ 开始,只要我们没有失配(并且没有到达末尾)就将其加 $1$ 。 诚然,这并不是一个高效的实现。我们接下来将展示一个高效实现的构造过程。 ## 计算 Z 函数的高效算法 -为了得到一个高效算法,我们将以 $i = 1$ 到 $n - 1$ 的顺序计算 $z[i]$,但在计算一个新值的同时,我们将尝试尽最大努力使用之前已经计算好的值。 +为了得到一个高效算法,我们将以 $i = 1$ 到 $n - 1$ 的顺序计算 $z[i]$ ,但在计算一个新值的同时,我们将尝试尽最大努力使用之前已经计算好的值。 -为了简便起见,定义**匹配段**为同 $s$ 一个前缀相同的那些子串。举例来说,所求 Z 函数的第 $i$ 个元素 $z[i]$ 为从位置 $i$ 开始的匹配段的长度(其终止位置位于 $i + z[i] - 1$)。 +为了简便起见,定义**匹配段**为同 $s$ 一个前缀相同的那些子串。举例来说,所求 Z 函数的第 $i$ 个元素 $z[i]$ 为从位置 $i$ 开始的匹配段的长度(其终止位置位于 $i + z[i] - 1$ )。 -为了达成目标,我们将始终保持**$[l;r]$ 为最靠右的匹配段**。也就是说,在所有已探测到的匹配段中,我们将保持结尾最靠右的那一个。另一方面,下标 $r$ 可被认为是字符串 $s$ 已被算法扫描的边界;任何超过该点的字符都是未知的。 +为了达成目标,我们将始终保持** $[l;r]$ 为最靠右的匹配段**。也就是说,在所有已探测到的匹配段中,我们将保持结尾最靠右的那一个。另一方面,下标 $r$ 可被认为是字符串 $s$ 已被算法扫描的边界;任何超过该点的字符都是未知的。 -假设当前下标为 $i$(即我们要计算的下一个 Z 函数值的下标),则有两种情况: +假设当前下标为 $i$ (即我们要计算的下一个 Z 函数值的下标),则有两种情况: -- $i > r$ -- 当前位置在我们已处理位置**之外**。 +- $i > r$ -- 当前位置在我们已处理位置**之外**。 - 我们接下来使用**朴素算法**(即一个一个的比较字符)来计算 $z[i]$。注意如果最后 $z[i] > 0$,我们需要更新最靠右的匹配段的下标,因为新的 $r = i + z[i] - 1$ 一定比之前的 $r$ 优。 + 我们接下来使用**朴素算法**(即一个一个的比较字符)来计算 $z[i]$ 。注意如果最后 $z[i] > 0$ ,我们需要更新最靠右的匹配段的下标,因为新的 $r = i + z[i] - 1$ 一定比之前的 $r$ 优。 -- $i \le r$ -- 当前位置位于当前匹配段 $[l;r]$ 之内。 +- $i \le r$ -- 当前位置位于当前匹配段 $[l;r]$ 之内。 - 那么我们可以用已计算过的 Z 函数值来 “初始化” $z[i]$ 至某值(至少比 “从零开始” 要好),甚至可能是某些较大的值。 + 那么我们可以用已计算过的 Z 函数值来“初始化” $z[i]$ 至某值(至少比“从零开始”要好),甚至可能是某些较大的值。 - 为了做到这一点,我们注意到子串 $s[l\dots r]$ 和 $s[0 \dots r - l]$ 匹配。这意味着作为 $z[i]$ 的一个初始近似,我们可以直接使用对应于段 $s[0 \dots r - l]$ 的已计算过的 Z 函数值,也即 $z[i - l]$。 + 为了做到这一点,我们注意到子串 $s[l\dots r]$ 和 $s[0 \dots r - l]$ 匹配。这意味着作为 $z[i]$ 的一个初始近似,我们可以直接使用对应于段 $s[0 \dots r - l]$ 的已计算过的 Z 函数值,也即 $z[i - l]$ 。 - 然而,$z[i - l]$ 可能太大了:将其应用到位置 $i$ 结果可能超过下标 $r$。这种做法并不合法,原因在于我们对 $r$ 右侧的字符一无所知:他们可能并不满足要求。 + 然而, $z[i - l]$ 可能太大了:将其应用到位置 $i$ 结果可能超过下标 $r$ 。这种做法并不合法,原因在于我们对 $r$ 右侧的字符一无所知:他们可能并不满足要求。 此处给出一个相似场景的**例子**: @@ -64,7 +64,7 @@ vector z_function_trivial(string s) { s=\mathtt{aaaabaa} $$ - 当我们尝试计算末尾位置($i = 6$)的值时,当前匹配的段为 $[5;6]$。位置 $6$ 会匹配位置 $6 - 5 = 1$,其 Z 函数值为 $z[1] = 3$。显然,我们不能将 $z[6]$ 初始化为 $3$,因为这完全不对。我们可以初始化的最大值为 $1$ -- 因为这是使我们不超过段 $[l;r]$ 的边界 $r$ 的最大可能取值。 + 当我们尝试计算末尾位置( $i = 6$ )的值时,当前匹配的段为 $[5;6]$ 。位置 $6$ 会匹配位置 $6 - 5 = 1$ ,其 Z 函数值为 $z[1] = 3$ 。显然,我们不能将 $z[6]$ 初始化为 $3$ ,因为这完全不对。我们可以初始化的最大值为 $1$ -- 因为这是使我们不超过段 $[l;r]$ 的边界 $r$ 的最大可能取值。 因此,我们可以放心的将下列值作为 $z[i]$ 的一个初始近似: @@ -74,7 +74,7 @@ vector z_function_trivial(string s) { 当将 $z[i]$ 初始化为 $z_0[i]$ 后,我们尝试使用**朴素算法**增加 $z[i]$ 的值 -- 因为宏观来讲,对于边界 $r$ 之后的事情,我们无法得知段是否会继续匹配还是失配。 -综上所述,整个算法被划分成两种情况,他们只在设置 $z[i]$ 的**初始值**时有所不同:在第一种情况下,其被认为为 $0$,在第二种情况下它由先前已计算过的值确定(使用前述公式)。之后,该算法的两个分支都被规约为实现**朴素算法**。当我们设置完初始值后,该算法即开始执行。 +综上所述,整个算法被划分成两种情况,他们只在设置 $z[i]$ 的**初始值**时有所不同:在第一种情况下,其被认为为 $0$ ,在第二种情况下它由先前已计算过的值确定(使用前述公式)。之后,该算法的两个分支都被规约为实现**朴素算法**。当我们设置完初始值后,该算法即开始执行。 该算法看起来非常简单。尽管在每轮迭代都会运行朴素算法,但我们已经取得了巨大进步:获得了一个时间复杂度为线性的算法。之后我们会证明这一点。 @@ -99,55 +99,55 @@ vector z_function(string s) { 整个解法被作为一个函数给出。该函数返回一个长度为 $n$ 的数组 -- $s$ 的 Z 函数。 -数组 $z$ 被初始化为全 $0$。当前最右的匹配段被假定为 $[0;0]$(一个故意为之的不包含任何 $i$ 的小段)。 +数组 $z$ 被初始化为全 $0$ 。当前最右的匹配段被假定为 $[0;0]$ (一个故意为之的不包含任何 $i$ 的小段)。 -在循环内,对于 $i=1\dots n - 1$,我们首先确定 $z[i]$ 的初始值 -- 其要么保持为 $0$ 或者使用前述公式计算。 +在循环内,对于 $i=1\dots n - 1$ ,我们首先确定 $z[i]$ 的初始值 -- 其要么保持为 $0$ 或者使用前述公式计算。 之后,朴素算法尝试尽可能多的增加 $z[i]$ 值。 -最后,如果必要(即如果 $i + z[i] - 1 > r$),我们更新最右匹配段 $[l;r]$。 +最后,如果必要(即如果 $i + z[i] - 1 > r$ ),我们更新最右匹配段 $[l;r]$ 。 ## 算法的渐进行为 -我们将证明上述算法的运行时间关于字符串长度呈线性 -- 即其时间复杂度为 $O(n)$。 +我们将证明上述算法的运行时间关于字符串长度呈线性 -- 即其时间复杂度为 $O(n)$ 。 该证明十分简单。 -我们只关心内层 `while` 循环,因为其余部分在一次循环中只是一堆常数次操作,其时间复杂度总和为 $O(n)$。 +我们只关心内层 `while` 循环,因为其余部分在一次循环中只是一堆常数次操作,其时间复杂度总和为 $O(n)$ 。 -我们将证明 `while` 的**每次迭代**都将增加匹配段的右边界 $r$。 +我们将证明 `while` 的**每次迭代**都将增加匹配段的右边界 $r$ 。 为了做到这一点,我们将考虑算法的所有分支: -- $i > r$ +- $i > r$ - 在这种情况下,要么 `while` 循环不进行任何迭代(如果 $s[0] \neq s[i]$),要么其将从位置 $i$ 开始进行若干次迭代,其中每次迭代将向右移动一个字符。每次迭代后,右边界 $r$ 必定被更新。 + 在这种情况下,要么 `while` 循环不进行任何迭代(如果 $s[0] \neq s[i]$ ),要么其将从位置 $i$ 开始进行若干次迭代,其中每次迭代将向右移动一个字符。每次迭代后,右边界 $r$ 必定被更新。 - 因此我们证明了,当 $i > r$ 时,`while` 循环的每轮迭代都会使新的 $r$ 增加 $1$。 + 因此我们证明了,当 $i > r$ 时, `while` 循环的每轮迭代都会使新的 $r$ 增加 $1$ 。 -- $i \le r$ +- $i \le r$ - 在这种情况下,我们将 $z[i]$ 初始化为由前述公式给出的某个具体 $z_0$。将 $z_0$ 和 $r - i + 1$ 比较,可能有三种情况: + 在这种情况下,我们将 $z[i]$ 初始化为由前述公式给出的某个具体 $z_0$ 。将 $z_0$ 和 $r - i + 1$ 比较,可能有三种情况: - - $z_0 < r - i + 1$ + - $z_0 < r - i + 1$ 我们证明在这种情况下 `while` 循环不会进行任何迭代。 这是十分容易证明的,比如通过反证法:如果 `while` 循环进行了至少一次迭代,这意味着初始近似 $z[i] = z_0$ 是不准确的(小于匹配的实际长度)。但是由于 $s[l\dots r]$ 和 $s[0\dots r - l]$ 是一样的,这推出 $z[i - l]$ 的值是错误的(比其该有的值小)。 - 所以,因为 $z[i - l]$ 是正确的且其值小于 $r - i + 1$,故该值同所求的 $z[i]$ 是相同的。 + 所以,因为 $z[i - l]$ 是正确的且其值小于 $r - i + 1$ ,故该值同所求的 $z[i]$ 是相同的。 - - $z_0 = r - i + 1$ + - $z_0 = r - i + 1$ - 在这种情况下,`while` 循环可能会进行若干次迭代。因为我们从 $s[r + 1]$ 开始比较,而其位置已经超过了区间 $[l;r]$,故每次迭代都会使 $r$ 增加。 + 在这种情况下, `while` 循环可能会进行若干次迭代。因为我们从 $s[r + 1]$ 开始比较,而其位置已经超过了区间 $[l;r]$ ,故每次迭代都会使 $r$ 增加。 - - $z_0 > r - i + 1$ + - $z_0 > r - i + 1$ 根据 $z_0$ 的定义,这种情况是不可能的。 -综上,我们已经证明了内层循环的每次迭代都会使 $r$ 向右移动。由于 $r$ 不可能超过 $n - 1$,这意味着内层循环至多进行 $n - 1$ 轮迭代。 +综上,我们已经证明了内层循环的每次迭代都会使 $r$ 向右移动。由于 $r$ 不可能超过 $n - 1$ ,这意味着内层循环至多进行 $n - 1$ 轮迭代。 -因为该算法的剩余部分显然时间复杂度为 $O(n)$,所以我们已经证明了计算 Z 函数的整个算法时间复杂度为线性。 +因为该算法的剩余部分显然时间复杂度为 $O(n)$ ,所以我们已经证明了计算 Z 函数的整个算法时间复杂度为线性。 ## 应用 @@ -159,41 +159,41 @@ vector z_function(string s) { 为了避免混淆,我们将 $t$ 称作**文本**,将 $p$ 称作**模式**。所给出的问题是:寻找在文本 $t$ 中模式 $p$ 的所有出现(occurrence)。 -为了解决该问题,我们构造一个新的字符串 $s = p + \diamond + t$,也即我们将 $p$ 和 $t$ 连接在一起,但是在中间放置了一个分割字符 $\diamond$(我们将如此选取 $\diamond$ 使得其必定不出现在 $p$ 和 $t$ 中)。 +为了解决该问题,我们构造一个新的字符串 $s = p + \diamond + t$ ,也即我们将 $p$ 和 $t$ 连接在一起,但是在中间放置了一个分割字符 $\diamond$ (我们将如此选取 $\diamond$ 使得其必定不出现在 $p$ 和 $t$ 中)。 -首先计算 $s$ 的 Z 函数。接下来,对于在区间 $[0; \operatorname{length}(t) - 1]$ 中的任意 $i$,我们考虑其对应的值 $k = z[i + \operatorname{length}(p) + 1]$。如果 $k$ 等于 $\operatorname{length}(p)$,那么我们知道有一个 $p$ 的出现位于 $t$ 的第 $i$ 个位置,否则没有 $p$ 的出现位于 $t$ 的第 $i$ 个位置。 +首先计算 $s$ 的 Z 函数。接下来,对于在区间 $[0; \operatorname{length}(t) - 1]$ 中的任意 $i$ ,我们考虑其对应的值 $k = z[i + \operatorname{length}(p) + 1]$ 。如果 $k$ 等于 $\operatorname{length}(p)$ ,那么我们知道有一个 $p$ 的出现位于 $t$ 的第 $i$ 个位置,否则没有 $p$ 的出现位于 $t$ 的第 $i$ 个位置。 -其时间复杂度(同时也是其空间复杂度)为 $O(\operatorname{length}(t) + \operatorname{length}(p))$。 +其时间复杂度(同时也是其空间复杂度)为 $O(\operatorname{length}(t) + \operatorname{length}(p))$ 。 ### 一个字符串中本质不同子串的数目 -给定一个长度为 $n$ 的字符串 $s$,计算 $s$ 的本质不同子串的数目。 +给定一个长度为 $n$ 的字符串 $s$ ,计算 $s$ 的本质不同子串的数目。 我们将迭代的解决该问题。也即:在知道了当前的本质不同子串的数目的情况下,在 $s$ 末尾添加一个字符后重新计算该数目。 -令 $k$ 为当前 $s$ 的本质不同子串数量。我们添加一个新的字符 $c$ 至 $s$。显然,会有一些新的子串以新的字符 $c$ 结尾(换句话说,那些以该字符结尾且我们之前未曾遇到的子串)。 +令 $k$ 为当前 $s$ 的本质不同子串数量。我们添加一个新的字符 $c$ 至 $s$ 。显然,会有一些新的子串以新的字符 $c$ 结尾(换句话说,那些以该字符结尾且我们之前未曾遇到的子串)。 -构造字符串 $t = s + c$ 并将其反转(以相反顺序书写其字符)。我们现在的任务是计算有多少 $t$ 的前缀未在 $t$ 的其余任何地方出现。让我们计算 $t$ 的 Z 函数并找到其最大值 $z_{\max}$。显然,$t$ 的长度为 $z_{\max}$ 的前缀出现在 $t$ 中间的某个位置。自然的,更短的前缀也出现了。 +构造字符串 $t = s + c$ 并将其反转(以相反顺序书写其字符)。我们现在的任务是计算有多少 $t$ 的前缀未在 $t$ 的其余任何地方出现。让我们计算 $t$ 的 Z 函数并找到其最大值 $z_{\max}$ 。显然, $t$ 的长度为 $z_{\max}$ 的前缀出现在 $t$ 中间的某个位置。自然的,更短的前缀也出现了。 -所以,我们已经找到了当将字符 $c$ 添加至 $s$ 后新出现的子串数目为 $\operatorname{length}(t) - z_{\max}$。 +所以,我们已经找到了当将字符 $c$ 添加至 $s$ 后新出现的子串数目为 $\operatorname{length}(t) - z_{\max}$ 。 -作为其结果,该解法对于一个长度为 $n$ 的字符串的时间复杂度为 $O(n^2)$。 +作为其结果,该解法对于一个长度为 $n$ 的字符串的时间复杂度为 $O(n^2)$ 。 值得注意的是,我们可以用同样的方法在 $O(n)$ 时间内,重新计算在头部添加一个字符,或者移除一个字符(从尾或者头)时的本质不同子串数目。 ### 字符串压缩 -给定一个长度为 $n$ 的字符串 $s$,找到其最短的 “压缩” 表示,即:寻找一个最短的字符串 $t$,使得 $s$ 可以被 $t$ 的一份或多份拷贝的拼接表示。 +给定一个长度为 $n$ 的字符串 $s$ ,找到其最短的“压缩”表示,即:寻找一个最短的字符串 $t$ ,使得 $s$ 可以被 $t$ 的一份或多份拷贝的拼接表示。 -其中一种解法为:计算 $s$ 的 Z 函数,从小到大循环所有满足 $i$ 整除 $n$ 的 $i$。在找到第一个满足 $i + z[i] = n$ 的 $i$ 时终止。那么该字符串 $s$ 可被压缩为长度 $i$ 的字符串。 +其中一种解法为:计算 $s$ 的 Z 函数,从小到大循环所有满足 $i$ 整除 $n$ 的 $i$ 。在找到第一个满足 $i + z[i] = n$ 的 $i$ 时终止。那么该字符串 $s$ 可被压缩为长度 $i$ 的字符串。 该事实的证明同应用[前缀函数](./prefix-function.md)的解法证明一样。 ## 练习题目 -- [Codeforces - Password \[Difficulty: Easy\]](http://codeforces.com/problemset/problem/126/B) -- [UVA # 455 "Periodic Strings" \[Difficulty: Medium\]](http://uva.onlinejudge.org/index.php?option=onlinejudge&page=show_problem&problem=396) -- [UVA # 11022 "String Factoring" \[Difficulty: Medium\]](http://uva.onlinejudge.org/index.php?option=onlinejudge&page=show_problem&problem=1963) +- [Codeforces - Password\[Difficulty: Easy\]](http://codeforces.com/problemset/problem/126/B) +- [UVA # 455 "Periodic Strings"\[Difficulty: Medium\]](http://uva.onlinejudge.org/index.php?option=onlinejudge&page=show_problem&problem=396) +- [UVA # 11022 "String Factoring"\[Difficulty: Medium\]](http://uva.onlinejudge.org/index.php?option=onlinejudge&page=show_problem&problem=1963) - [UVa 11475 - Extend to Palindrome](http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=24&page=show_problem&problem=2470) - [LA 6439 - Pasti Pas!](https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&category=588&page=show_problem&problem=4450) - [Codechef - Chef and Strings](https://www.codechef.com/problems/CHSTR) @@ -201,4 +201,4 @@ vector z_function(string s) { * * * -**本页面主要译自博文 [Z-функция строки и её вычисление](http://e-maxx.ru/algo/z_function) 与其英文翻译版 [Z-function and its calculation](https://cp-algorithms.com/string/z-function.html) 。其中俄文版版权协议为 Public Domain + Leave a Link;英文版版权协议为 CC-BY-SA 4.0。** +**本页面主要译自博文[Z-функция строки и её вычисление](http://e-maxx.ru/algo/z_function)与其英文翻译版[Z-function and its calculation](https://cp-algorithms.com/string/z-function.html)。其中俄文版版权协议为 Public Domain + Leave a Link;英文版版权协议为 CC-BY-SA 4.0。** diff --git a/package-lock.json b/package-lock.json index 251d959c..2fef987f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,7 @@ "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.1.0.tgz", "integrity": "sha512-KjZwU26uG3u6eZcfGbTULzFcsoz6pegNKtHPksZPOUsiKo5bUmiBPa38FuHZ/Eun+XYh/JCCkS9AS3Lu4McQOQ==", "requires": { - "acorn": "^5.0.0" + "acorn": "5.7.2" } }, "ajv": { @@ -32,10 +32,10 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" + "co": "4.6.0", + "fast-deep-equal": "1.1.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" } }, "alphanum-sort": { @@ -48,7 +48,7 @@ "resolved": "http://registry.npm.taobao.org/ansi-colors/download/ansi-colors-1.1.0.tgz", "integrity": "sha1-Y3S03V1HGP884npnGjscrQdxMqk=", "requires": { - "ansi-wrap": "^0.1.0" + "ansi-wrap": "0.1.0" } }, "ansi-gray": { @@ -79,8 +79,8 @@ "resolved": "http://registry.npm.taobao.org/anymatch/download/anymatch-2.0.0.tgz", "integrity": "sha1-vLJLTzeTTZqnrBe0ra+J58du8us=", "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" + "micromatch": "3.1.10", + "normalize-path": "2.1.1" } }, "append-buffer": { @@ -88,7 +88,7 @@ "resolved": "http://registry.npm.taobao.org/append-buffer/download/append-buffer-1.0.2.tgz", "integrity": "sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE=", "requires": { - "buffer-equal": "^1.0.0" + "buffer-equal": "1.0.0" } }, "archy": { @@ -101,7 +101,7 @@ "resolved": "http://registry.npm.taobao.org/argparse/download/argparse-1.0.10.tgz", "integrity": "sha1-vNZ5HqWuCXJeF+WtmIE0zUCz2RE=", "requires": { - "sprintf-js": "~1.0.2" + "sprintf-js": "1.0.3" } }, "arr-diff": { @@ -114,7 +114,7 @@ "resolved": "http://registry.npm.taobao.org/arr-filter/download/arr-filter-1.1.2.tgz", "integrity": "sha1-Q/3d0JHo7xGqTEXZzcGOLf8XEe4=", "requires": { - "make-iterator": "^1.0.0" + "make-iterator": "1.0.1" } }, "arr-flatten": { @@ -127,7 +127,7 @@ "resolved": "http://registry.npm.taobao.org/arr-map/download/arr-map-2.0.2.tgz", "integrity": "sha1-Onc0X/wc814qkYJWAfnljy4kysQ=", "requires": { - "make-iterator": "^1.0.0" + "make-iterator": "1.0.1" } }, "arr-union": { @@ -155,8 +155,8 @@ "resolved": "http://registry.npm.taobao.org/array-initial/download/array-initial-1.1.0.tgz", "integrity": "sha1-L6dLJnOTccOUe9enrcc74zSz15U=", "requires": { - "array-slice": "^1.0.0", - "is-number": "^4.0.0" + "array-slice": "1.1.0", + "is-number": "4.0.0" }, "dependencies": { "is-number": { @@ -171,7 +171,7 @@ "resolved": "http://registry.npm.taobao.org/array-last/download/array-last-1.3.0.tgz", "integrity": "sha1-eqdwc/7FZd2rJJP1+IGF9ASp0zY=", "requires": { - "is-number": "^4.0.0" + "is-number": "4.0.0" }, "dependencies": { "is-number": { @@ -191,9 +191,9 @@ "resolved": "http://registry.npm.taobao.org/array-sort/download/array-sort-1.0.0.tgz", "integrity": "sha1-5MBTVkU/VvU1EqfR1hI/LFTAqIo=", "requires": { - "default-compare": "^1.0.0", - "get-value": "^2.0.6", - "kind-of": "^5.0.2" + "default-compare": "1.0.0", + "get-value": "2.0.6", + "kind-of": "5.1.0" }, "dependencies": { "kind-of": { @@ -218,7 +218,7 @@ "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", "requires": { - "safer-buffer": "~2.1.0" + "safer-buffer": "2.1.2" } }, "assert-plus": { @@ -241,10 +241,10 @@ "resolved": "http://registry.npm.taobao.org/async-done/download/async-done-1.3.1.tgz", "integrity": "sha1-FLe3Nme4ZMjwK1slP8nG7dt3fz4=", "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.2", - "process-nextick-args": "^1.0.7", - "stream-exhaust": "^1.0.1" + "end-of-stream": "1.4.1", + "once": "1.4.0", + "process-nextick-args": "1.0.7", + "stream-exhaust": "1.0.2" } }, "async-each": { @@ -262,7 +262,7 @@ "resolved": "http://registry.npm.taobao.org/async-settle/download/async-settle-1.0.0.tgz", "integrity": "sha1-HQqRS7Aldb7IqPOnTlCA9yssDGs=", "requires": { - "async-done": "^1.2.2" + "async-done": "1.3.1" } }, "asynckit": { @@ -290,15 +290,15 @@ "resolved": "http://registry.npm.taobao.org/bach/download/bach-1.2.0.tgz", "integrity": "sha1-Szzpa/JxNPeaG0FKUcFONMO9mIA=", "requires": { - "arr-filter": "^1.1.1", - "arr-flatten": "^1.0.1", - "arr-map": "^2.0.0", - "array-each": "^1.0.0", - "array-initial": "^1.0.0", - "array-last": "^1.1.1", - "async-done": "^1.2.2", - "async-settle": "^1.0.0", - "now-and-later": "^2.0.0" + "arr-filter": "1.1.2", + "arr-flatten": "1.1.0", + "arr-map": "2.0.2", + "array-each": "1.0.1", + "array-initial": "1.1.0", + "array-last": "1.3.0", + "async-done": "1.3.1", + "async-settle": "1.0.0", + "now-and-later": "2.0.0" } }, "balanced-match": { @@ -311,13 +311,13 @@ "resolved": "http://registry.npm.taobao.org/base/download/base-0.11.2.tgz", "integrity": "sha1-e95c7RRbbVUakNuH+DxVi060io8=", "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" + "cache-base": "1.0.1", + "class-utils": "0.3.6", + "component-emitter": "1.2.1", + "define-property": "1.0.0", + "isobject": "3.0.1", + "mixin-deep": "1.3.1", + "pascalcase": "0.1.1" }, "dependencies": { "define-property": { @@ -325,7 +325,7 @@ "resolved": "http://registry.npm.taobao.org/define-property/download/define-property-1.0.0.tgz", "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "requires": { - "is-descriptor": "^1.0.0" + "is-descriptor": "1.0.2" } }, "is-accessor-descriptor": { @@ -333,7 +333,7 @@ "resolved": "http://registry.npm.taobao.org/is-accessor-descriptor/download/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.2" } }, "is-data-descriptor": { @@ -341,7 +341,7 @@ "resolved": "http://registry.npm.taobao.org/is-data-descriptor/download/is-data-descriptor-1.0.0.tgz", "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.2" } }, "is-descriptor": { @@ -349,9 +349,9 @@ "resolved": "http://registry.npm.taobao.org/is-descriptor/download/is-descriptor-1.0.2.tgz", "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" } } } @@ -362,7 +362,7 @@ "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", "optional": true, "requires": { - "tweetnacl": "^0.14.3" + "tweetnacl": "0.14.5" } }, "beeper": { @@ -385,7 +385,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "requires": { - "balanced-match": "^1.0.0", + "balanced-match": "1.0.0", "concat-map": "0.0.1" } }, @@ -394,16 +394,16 @@ "resolved": "http://registry.npm.taobao.org/braces/download/braces-2.3.2.tgz", "integrity": "sha1-WXn9PxTNUxVl5fot8av/8d+u5yk=", "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" + "arr-flatten": "1.1.0", + "array-unique": "0.3.2", + "extend-shallow": "2.0.1", + "fill-range": "4.0.0", + "isobject": "3.0.1", + "repeat-element": "1.1.3", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.2" }, "dependencies": { "extend-shallow": { @@ -411,7 +411,7 @@ "resolved": "http://registry.npm.taobao.org/extend-shallow/download/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -426,9 +426,9 @@ "resolved": "http://registry.npm.taobao.org/browserslist/download/browserslist-4.3.6.tgz", "integrity": "sha1-D52Qga/Gazb0d8a984E/eE9COWo=", "requires": { - "caniuse-lite": "^1.0.30000921", - "electron-to-chromium": "^1.3.92", - "node-releases": "^1.1.1" + "caniuse-lite": "1.0.30000923", + "electron-to-chromium": "1.3.96", + "node-releases": "1.1.2" } }, "buffer-equal": { @@ -451,15 +451,15 @@ "resolved": "http://registry.npm.taobao.org/cache-base/download/cache-base-1.0.1.tgz", "integrity": "sha1-Cn9GQWgxyLZi7jb+TnxZ129marI=", "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" + "collection-visit": "1.0.0", + "component-emitter": "1.2.1", + "get-value": "2.0.6", + "has-value": "1.0.0", + "isobject": "3.0.1", + "set-value": "2.0.0", + "to-object-path": "0.3.0", + "union-value": "1.0.0", + "unset-value": "1.0.0" } }, "caller-callsite": { @@ -467,7 +467,7 @@ "resolved": "http://registry.npm.taobao.org/caller-callsite/download/caller-callsite-2.0.0.tgz", "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", "requires": { - "callsites": "^2.0.0" + "callsites": "2.0.0" } }, "caller-path": { @@ -475,7 +475,7 @@ "resolved": "http://registry.npm.taobao.org/caller-path/download/caller-path-2.0.0.tgz", "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", "requires": { - "caller-callsite": "^2.0.0" + "caller-callsite": "2.0.0" } }, "callsites": { @@ -488,8 +488,8 @@ "resolved": "http://registry.npm.taobao.org/camel-case/download/camel-case-3.0.0.tgz", "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=", "requires": { - "no-case": "^2.2.0", - "upper-case": "^1.1.1" + "no-case": "2.3.2", + "upper-case": "1.1.3" } }, "camelcase": { @@ -502,10 +502,10 @@ "resolved": "http://registry.npm.taobao.org/caniuse-api/download/caniuse-api-3.0.0.tgz", "integrity": "sha1-Xk2Q4idJYdRikZl99Znj7QCO5MA=", "requires": { - "browserslist": "^4.0.0", - "caniuse-lite": "^1.0.0", - "lodash.memoize": "^4.1.2", - "lodash.uniq": "^4.5.0" + "browserslist": "4.3.6", + "caniuse-lite": "1.0.30000923", + "lodash.memoize": "4.1.2", + "lodash.uniq": "4.5.0" } }, "caniuse-lite": { @@ -523,11 +523,11 @@ "resolved": "http://registry.npm.taobao.org/chalk/download/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" }, "dependencies": { "ansi-regex": { @@ -540,7 +540,7 @@ "resolved": "http://registry.npm.taobao.org/strip-ansi/download/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "2.1.1" } } } @@ -550,19 +550,19 @@ "resolved": "http://registry.npm.taobao.org/chokidar/download/chokidar-2.0.4.tgz", "integrity": "sha1-NW/04rDo5D4yLRijckYLvPOszSY=", "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.0", - "braces": "^2.3.0", - "fsevents": "^1.2.2", - "glob-parent": "^3.1.0", - "inherits": "^2.0.1", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "lodash.debounce": "^4.0.8", - "normalize-path": "^2.1.1", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.0.0", - "upath": "^1.0.5" + "anymatch": "2.0.0", + "async-each": "1.0.1", + "braces": "2.3.2", + "fsevents": "1.2.4", + "glob-parent": "3.1.0", + "inherits": "2.0.3", + "is-binary-path": "1.0.1", + "is-glob": "4.0.0", + "lodash.debounce": "4.0.8", + "normalize-path": "2.1.1", + "path-is-absolute": "1.0.1", + "readdirp": "2.2.1", + "upath": "1.1.0" } }, "clang-format": { @@ -570,9 +570,9 @@ "resolved": "https://registry.npmjs.org/clang-format/-/clang-format-1.2.4.tgz", "integrity": "sha512-sw+nrGUp3hvmANd1qF8vZPuezSYQAiXgGBiEtkXTtJnnu6b00fCqkkDIsnRKrNgg4nv6NYZE92ejvOMIXZoejw==", "requires": { - "async": "^1.5.2", - "glob": "^7.0.0", - "resolve": "^1.1.6" + "async": "1.5.2", + "glob": "7.1.3", + "resolve": "1.8.1" } }, "class-utils": { @@ -580,10 +580,10 @@ "resolved": "http://registry.npm.taobao.org/class-utils/download/class-utils-0.3.6.tgz", "integrity": "sha1-+TNprouafOAv1B+q0MqDAzGQxGM=", "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" + "arr-union": "3.1.0", + "define-property": "0.2.5", + "isobject": "3.0.1", + "static-extend": "0.1.2" }, "dependencies": { "define-property": { @@ -591,7 +591,7 @@ "resolved": "http://registry.npm.taobao.org/define-property/download/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "requires": { - "is-descriptor": "^0.1.0" + "is-descriptor": "0.1.6" } } } @@ -601,7 +601,7 @@ "resolved": "http://registry.npm.taobao.org/clean-css/download/clean-css-4.2.1.tgz", "integrity": "sha1-LUEe92uFabbQyEBo2r6FsKpeXBc=", "requires": { - "source-map": "~0.6.0" + "source-map": "0.6.1" } }, "cliui": { @@ -609,9 +609,9 @@ "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" + "string-width": "2.1.1", + "strip-ansi": "4.0.0", + "wrap-ansi": "2.1.0" } }, "clone": { @@ -634,9 +634,9 @@ "resolved": "http://registry.npm.taobao.org/cloneable-readable/download/cloneable-readable-1.1.2.tgz", "integrity": "sha1-1ZHe5Kj4vBXaQ86X3O66E9Q+KmU=", "requires": { - "inherits": "^2.0.1", - "process-nextick-args": "^2.0.0", - "readable-stream": "^2.3.5" + "inherits": "2.0.3", + "process-nextick-args": "2.0.0", + "readable-stream": "2.3.6" }, "dependencies": { "process-nextick-args": { @@ -656,9 +656,9 @@ "resolved": "http://registry.npm.taobao.org/coa/download/coa-2.0.2.tgz", "integrity": "sha1-Q/bCEVG07yv1cYfbDXPeIp4+fsM=", "requires": { - "@types/q": "^1.5.1", - "chalk": "^2.4.1", - "q": "^1.1.2" + "@types/q": "1.5.1", + "chalk": "2.4.1", + "q": "1.5.1" }, "dependencies": { "ansi-styles": { @@ -666,7 +666,7 @@ "resolved": "http://registry.npm.taobao.org/ansi-styles/download/ansi-styles-3.2.1.tgz", "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", "requires": { - "color-convert": "^1.9.0" + "color-convert": "1.9.3" } }, "chalk": { @@ -674,9 +674,9 @@ "resolved": "http://registry.npm.taobao.org/chalk/download/chalk-2.4.1.tgz", "integrity": "sha1-GMSasWoDe26wFSzIPjRxM4IVtm4=", "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.5.0" } }, "supports-color": { @@ -684,7 +684,7 @@ "resolved": "http://registry.npm.taobao.org/supports-color/download/supports-color-5.5.0.tgz", "integrity": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", "requires": { - "has-flag": "^3.0.0" + "has-flag": "3.0.0" } } } @@ -699,9 +699,9 @@ "resolved": "http://registry.npm.taobao.org/collection-map/download/collection-map-1.0.0.tgz", "integrity": "sha1-rqDwb40mx4DCt1SUOFVEsiVa8Yw=", "requires": { - "arr-map": "^2.0.2", - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" + "arr-map": "2.0.2", + "for-own": "1.0.0", + "make-iterator": "1.0.1" } }, "collection-visit": { @@ -709,8 +709,8 @@ "resolved": "http://registry.npm.taobao.org/collection-visit/download/collection-visit-1.0.0.tgz", "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" + "map-visit": "1.0.0", + "object-visit": "1.0.1" } }, "color": { @@ -718,8 +718,8 @@ "resolved": "http://registry.npm.taobao.org/color/download/color-3.1.0.tgz", "integrity": "sha1-2On7CWcyh1d0yEv5IoFd8DCND/w=", "requires": { - "color-convert": "^1.9.1", - "color-string": "^1.5.2" + "color-convert": "1.9.3", + "color-string": "1.5.3" } }, "color-convert": { @@ -740,8 +740,8 @@ "resolved": "http://registry.npm.taobao.org/color-string/download/color-string-1.5.3.tgz", "integrity": "sha1-ybvF8BtYtUkvPWhXRZy2WQziBMw=", "requires": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" + "color-name": "1.1.3", + "simple-swizzle": "0.2.2" } }, "color-support": { @@ -759,7 +759,7 @@ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", "requires": { - "delayed-stream": "~1.0.0" + "delayed-stream": "1.0.0" } }, "commander": { @@ -782,10 +782,10 @@ "resolved": "http://registry.npm.taobao.org/concat-stream/download/concat-stream-1.6.2.tgz", "integrity": "sha1-kEvfGUzTEi/Gdcd/xKw9T/D9GjQ=", "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" + "buffer-from": "1.1.1", + "inherits": "2.0.3", + "readable-stream": "2.3.6", + "typedarray": "0.0.6" } }, "convert-source-map": { @@ -793,7 +793,7 @@ "resolved": "http://registry.npm.taobao.org/convert-source-map/download/convert-source-map-1.6.0.tgz", "integrity": "sha1-UbU3qMQ+DwTewZk7/83VBOdYrCA=", "requires": { - "safe-buffer": "~5.1.1" + "safe-buffer": "5.1.2" } }, "copy-descriptor": { @@ -806,8 +806,8 @@ "resolved": "http://registry.npm.taobao.org/copy-props/download/copy-props-2.0.4.tgz", "integrity": "sha1-k7scrfr9MdpbuKnUtB9HHsOnLf4=", "requires": { - "each-props": "^1.3.0", - "is-plain-object": "^2.0.1" + "each-props": "1.3.2", + "is-plain-object": "2.0.4" } }, "copywriting-correct": { @@ -825,10 +825,10 @@ "resolved": "http://registry.npm.taobao.org/cosmiconfig/download/cosmiconfig-5.0.7.tgz", "integrity": "sha1-OYJrKS7g147aE336MXO9HCGkOwQ=", "requires": { - "import-fresh": "^2.0.0", - "is-directory": "^0.3.1", - "js-yaml": "^3.9.0", - "parse-json": "^4.0.0" + "import-fresh": "2.0.0", + "is-directory": "0.3.1", + "js-yaml": "3.12.0", + "parse-json": "4.0.0" }, "dependencies": { "parse-json": { @@ -836,8 +836,8 @@ "resolved": "http://registry.npm.taobao.org/parse-json/download/parse-json-4.0.0.tgz", "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" + "error-ex": "1.3.2", + "json-parse-better-errors": "1.0.2" } } } @@ -847,9 +847,9 @@ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", "requires": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "lru-cache": "4.1.3", + "shebang-command": "1.2.0", + "which": "1.3.1" } }, "css": { @@ -857,10 +857,10 @@ "resolved": "http://registry.npm.taobao.org/css/download/css-2.2.4.tgz", "integrity": "sha1-xkZ1XHOXHyu6amAeLPL9cbEpiSk=", "requires": { - "inherits": "^2.0.3", - "source-map": "^0.6.1", - "source-map-resolve": "^0.5.2", - "urix": "^0.1.0" + "inherits": "2.0.3", + "source-map": "0.6.1", + "source-map-resolve": "0.5.2", + "urix": "0.1.0" } }, "css-color-names": { @@ -873,8 +873,8 @@ "resolved": "http://registry.npm.taobao.org/css-declaration-sorter/download/css-declaration-sorter-4.0.1.tgz", "integrity": "sha1-wZiUD2OnbX42wecQGLABchBUyyI=", "requires": { - "postcss": "^7.0.1", - "timsort": "^0.3.0" + "postcss": "7.0.7", + "timsort": "0.3.0" } }, "css-select": { @@ -882,10 +882,10 @@ "resolved": "http://registry.npm.taobao.org/css-select/download/css-select-2.0.2.tgz", "integrity": "sha1-q0OGzsnh9miFVWSxfDcztDsqXt4=", "requires": { - "boolbase": "^1.0.0", - "css-what": "^2.1.2", - "domutils": "^1.7.0", - "nth-check": "^1.0.2" + "boolbase": "1.0.0", + "css-what": "2.1.2", + "domutils": "1.7.0", + "nth-check": "1.0.2" } }, "css-select-base-adapter": { @@ -898,8 +898,8 @@ "resolved": "http://registry.npm.taobao.org/css-tree/download/css-tree-1.0.0-alpha.28.tgz", "integrity": "sha1-joloGQ2IbJR3vI1h6W9hrz9/+n8=", "requires": { - "mdn-data": "~1.1.0", - "source-map": "^0.5.3" + "mdn-data": "1.1.4", + "source-map": "0.5.7" }, "dependencies": { "source-map": { @@ -934,10 +934,10 @@ "resolved": "http://registry.npm.taobao.org/cssnano/download/cssnano-4.1.8.tgz", "integrity": "sha1-gBSYlnnV/UJJHkSZpSHb+4XJX9E=", "requires": { - "cosmiconfig": "^5.0.0", - "cssnano-preset-default": "^4.0.6", - "is-resolvable": "^1.0.0", - "postcss": "^7.0.0" + "cosmiconfig": "5.0.7", + "cssnano-preset-default": "4.0.6", + "is-resolvable": "1.1.0", + "postcss": "7.0.7" } }, "cssnano-preset-default": { @@ -945,36 +945,36 @@ "resolved": "http://registry.npm.taobao.org/cssnano-preset-default/download/cssnano-preset-default-4.0.6.tgz", "integrity": "sha1-kjeeKm20qRwOpyf19VburGk+q2o=", "requires": { - "css-declaration-sorter": "^4.0.1", - "cssnano-util-raw-cache": "^4.0.1", - "postcss": "^7.0.0", - "postcss-calc": "^7.0.0", - "postcss-colormin": "^4.0.2", - "postcss-convert-values": "^4.0.1", - "postcss-discard-comments": "^4.0.1", - "postcss-discard-duplicates": "^4.0.2", - "postcss-discard-empty": "^4.0.1", - "postcss-discard-overridden": "^4.0.1", - "postcss-merge-longhand": "^4.0.10", - "postcss-merge-rules": "^4.0.2", - "postcss-minify-font-values": "^4.0.2", - "postcss-minify-gradients": "^4.0.1", - "postcss-minify-params": "^4.0.1", - "postcss-minify-selectors": "^4.0.1", - "postcss-normalize-charset": "^4.0.1", - "postcss-normalize-display-values": "^4.0.1", - "postcss-normalize-positions": "^4.0.1", - "postcss-normalize-repeat-style": "^4.0.1", - "postcss-normalize-string": "^4.0.1", - "postcss-normalize-timing-functions": "^4.0.1", - "postcss-normalize-unicode": "^4.0.1", - "postcss-normalize-url": "^4.0.1", - "postcss-normalize-whitespace": "^4.0.1", - "postcss-ordered-values": "^4.1.1", - "postcss-reduce-initial": "^4.0.2", - "postcss-reduce-transforms": "^4.0.1", - "postcss-svgo": "^4.0.1", - "postcss-unique-selectors": "^4.0.1" + "css-declaration-sorter": "4.0.1", + "cssnano-util-raw-cache": "4.0.1", + "postcss": "7.0.7", + "postcss-calc": "7.0.1", + "postcss-colormin": "4.0.2", + "postcss-convert-values": "4.0.1", + "postcss-discard-comments": "4.0.1", + "postcss-discard-duplicates": "4.0.2", + "postcss-discard-empty": "4.0.1", + "postcss-discard-overridden": "4.0.1", + "postcss-merge-longhand": "4.0.10", + "postcss-merge-rules": "4.0.2", + "postcss-minify-font-values": "4.0.2", + "postcss-minify-gradients": "4.0.1", + "postcss-minify-params": "4.0.1", + "postcss-minify-selectors": "4.0.1", + "postcss-normalize-charset": "4.0.1", + "postcss-normalize-display-values": "4.0.1", + "postcss-normalize-positions": "4.0.1", + "postcss-normalize-repeat-style": "4.0.1", + "postcss-normalize-string": "4.0.1", + "postcss-normalize-timing-functions": "4.0.1", + "postcss-normalize-unicode": "4.0.1", + "postcss-normalize-url": "4.0.1", + "postcss-normalize-whitespace": "4.0.1", + "postcss-ordered-values": "4.1.1", + "postcss-reduce-initial": "4.0.2", + "postcss-reduce-transforms": "4.0.1", + "postcss-svgo": "4.0.1", + "postcss-unique-selectors": "4.0.1" } }, "cssnano-util-get-arguments": { @@ -992,7 +992,7 @@ "resolved": "http://registry.npm.taobao.org/cssnano-util-raw-cache/download/cssnano-util-raw-cache-4.0.1.tgz", "integrity": "sha1-sm1f1fcqEd/np4RvtMZyYPlr8oI=", "requires": { - "postcss": "^7.0.0" + "postcss": "7.0.7" } }, "cssnano-util-same-parent": { @@ -1013,8 +1013,8 @@ "resolved": "http://registry.npm.taobao.org/css-tree/download/css-tree-1.0.0-alpha.29.tgz", "integrity": "sha1-P6nU7zFCy9HDAedmTB81K9gvWjk=", "requires": { - "mdn-data": "~1.1.0", - "source-map": "^0.5.3" + "mdn-data": "1.1.4", + "source-map": "0.5.7" } }, "source-map": { @@ -1034,7 +1034,7 @@ "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.1.1.tgz", "integrity": "sha512-364AI1l/M5TYcFH83JnOH/pSqgaNnKmYgKrm0didZMGKWjQB60dymwWy1rKUgL3J1ffdq9xVi2yGLHdSjjSNog==", "requires": { - "cssom": "0.3.x" + "cssom": "0.3.4" } }, "d": { @@ -1042,7 +1042,7 @@ "resolved": "http://registry.npm.taobao.org/d/download/d-1.0.0.tgz", "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", "requires": { - "es5-ext": "^0.10.9" + "es5-ext": "0.10.46" } }, "dashdash": { @@ -1050,7 +1050,7 @@ "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", "requires": { - "assert-plus": "^1.0.0" + "assert-plus": "1.0.0" } }, "data-urls": { @@ -1058,9 +1058,9 @@ "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.0.1.tgz", "integrity": "sha512-0HdcMZzK6ubMUnsMmQmG0AcLQPvbvb47R0+7CCZQCYgcd8OUWG91CG7sM6GoXgjz+WLl4ArFzHtBMy/QqSF4eg==", "requires": { - "abab": "^2.0.0", - "whatwg-mimetype": "^2.1.0", - "whatwg-url": "^7.0.0" + "abab": "2.0.0", + "whatwg-mimetype": "2.1.0", + "whatwg-url": "7.0.0" }, "dependencies": { "whatwg-url": { @@ -1068,9 +1068,9 @@ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.0.0.tgz", "integrity": "sha512-37GeVSIJ3kn1JgKyjiYNmSLP1yzbpb29jdmwBSgkD9h40/hyrR/OifpVUndji3tmwGgD8qpw7iQu3RSbCrBpsQ==", "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" + "lodash.sortby": "4.7.0", + "tr46": "1.0.1", + "webidl-conversions": "4.0.2" } } } @@ -1108,7 +1108,7 @@ "resolved": "http://registry.npm.taobao.org/default-compare/download/default-compare-1.0.0.tgz", "integrity": "sha1-y2ETGESthNhHiPto/QFoHKd4Gi8=", "requires": { - "kind-of": "^5.0.2" + "kind-of": "5.1.0" }, "dependencies": { "kind-of": { @@ -1128,7 +1128,7 @@ "resolved": "http://registry.npm.taobao.org/define-properties/download/define-properties-1.1.3.tgz", "integrity": "sha1-z4jabL7ib+bbcJT2HYcMvYTO6fE=", "requires": { - "object-keys": "^1.0.12" + "object-keys": "1.0.12" } }, "define-property": { @@ -1136,8 +1136,8 @@ "resolved": "http://registry.npm.taobao.org/define-property/download/define-property-2.0.2.tgz", "integrity": "sha1-1Flono1lS6d+AqgX+HENcCyxbp0=", "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" + "is-descriptor": "1.0.2", + "isobject": "3.0.1" }, "dependencies": { "is-accessor-descriptor": { @@ -1145,7 +1145,7 @@ "resolved": "http://registry.npm.taobao.org/is-accessor-descriptor/download/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.2" } }, "is-data-descriptor": { @@ -1153,7 +1153,7 @@ "resolved": "http://registry.npm.taobao.org/is-data-descriptor/download/is-data-descriptor-1.0.0.tgz", "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.2" } }, "is-descriptor": { @@ -1161,9 +1161,9 @@ "resolved": "http://registry.npm.taobao.org/is-descriptor/download/is-descriptor-1.0.2.tgz", "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" } } } @@ -1183,8 +1183,8 @@ "resolved": "http://registry.npm.taobao.org/dom-serializer/download/dom-serializer-0.1.0.tgz", "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", "requires": { - "domelementtype": "~1.1.1", - "entities": "~1.1.1" + "domelementtype": "1.1.3", + "entities": "1.1.2" }, "dependencies": { "domelementtype": { @@ -1204,7 +1204,7 @@ "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", "requires": { - "webidl-conversions": "^4.0.2" + "webidl-conversions": "4.0.2" } }, "domutils": { @@ -1212,8 +1212,8 @@ "resolved": "http://registry.npm.taobao.org/domutils/download/domutils-1.7.0.tgz", "integrity": "sha1-Vuo0HoNOBuZ0ivehyyXaZ+qfjCo=", "requires": { - "dom-serializer": "0", - "domelementtype": "1" + "dom-serializer": "0.1.0", + "domelementtype": "1.3.1" } }, "dot-prop": { @@ -1221,7 +1221,7 @@ "resolved": "http://registry.npm.taobao.org/dot-prop/download/dot-prop-4.2.0.tgz", "integrity": "sha1-HxngwuGqDjJ5fEl5nyg3rGr2nFc=", "requires": { - "is-obj": "^1.0.0" + "is-obj": "1.0.1" } }, "duplexer2": { @@ -1229,7 +1229,7 @@ "resolved": "http://registry.npm.taobao.org/duplexer2/download/duplexer2-0.0.2.tgz", "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", "requires": { - "readable-stream": "~1.1.9" + "readable-stream": "1.1.14" }, "dependencies": { "isarray": { @@ -1242,10 +1242,10 @@ "resolved": "http://registry.npm.taobao.org/readable-stream/download/readable-stream-1.1.14.tgz", "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", + "core-util-is": "1.0.2", + "inherits": "2.0.3", "isarray": "0.0.1", - "string_decoder": "~0.10.x" + "string_decoder": "0.10.31" } }, "string_decoder": { @@ -1260,10 +1260,10 @@ "resolved": "http://registry.npm.taobao.org/duplexify/download/duplexify-3.6.1.tgz", "integrity": "sha1-saeinEq/1jlYXvrszoDWZrHjQSU=", "requires": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" + "end-of-stream": "1.4.1", + "inherits": "2.0.3", + "readable-stream": "2.3.6", + "stream-shift": "1.0.0" } }, "each-props": { @@ -1271,8 +1271,8 @@ "resolved": "http://registry.npm.taobao.org/each-props/download/each-props-1.3.2.tgz", "integrity": "sha1-6kWkFNFt1c+kGbGoFyDVygaJIzM=", "requires": { - "is-plain-object": "^2.0.1", - "object.defaults": "^1.1.0" + "is-plain-object": "2.0.4", + "object.defaults": "1.1.0" } }, "ecc-jsbn": { @@ -1281,8 +1281,8 @@ "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", "optional": true, "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" + "jsbn": "0.1.1", + "safer-buffer": "2.1.2" } }, "electron-to-chromium": { @@ -1295,7 +1295,7 @@ "resolved": "http://registry.npm.taobao.org/end-of-stream/download/end-of-stream-1.4.1.tgz", "integrity": "sha1-7SljTRm6ukY7bOa4CjchPqtx7EM=", "requires": { - "once": "^1.4.0" + "once": "1.4.0" } }, "entities": { @@ -1308,7 +1308,7 @@ "resolved": "http://registry.npm.taobao.org/error-ex/download/error-ex-1.3.2.tgz", "integrity": "sha1-tKxAZIEH/c3PriQvQovqihTU8b8=", "requires": { - "is-arrayish": "^0.2.1" + "is-arrayish": "0.2.1" } }, "es-abstract": { @@ -1316,11 +1316,11 @@ "resolved": "http://registry.npm.taobao.org/es-abstract/download/es-abstract-1.12.0.tgz", "integrity": "sha1-nbvdJ8aFbwABQhyhh4LXhr+KYWU=", "requires": { - "es-to-primitive": "^1.1.1", - "function-bind": "^1.1.1", - "has": "^1.0.1", - "is-callable": "^1.1.3", - "is-regex": "^1.0.4" + "es-to-primitive": "1.2.0", + "function-bind": "1.1.1", + "has": "1.0.3", + "is-callable": "1.1.4", + "is-regex": "1.0.4" } }, "es-to-primitive": { @@ -1328,9 +1328,9 @@ "resolved": "http://registry.npm.taobao.org/es-to-primitive/download/es-to-primitive-1.2.0.tgz", "integrity": "sha1-7fckeAM0VujdqO8J4ArZZQcH83c=", "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" + "is-callable": "1.1.4", + "is-date-object": "1.0.1", + "is-symbol": "1.0.2" } }, "es5-ext": { @@ -1338,9 +1338,9 @@ "resolved": "http://registry.npm.taobao.org/es5-ext/download/es5-ext-0.10.46.tgz", "integrity": "sha1-79mfZ8Wn7Hibqj2qf3mHA4j39XI=", "requires": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.1", - "next-tick": "1" + "es6-iterator": "2.0.3", + "es6-symbol": "3.1.1", + "next-tick": "1.0.0" } }, "es6-iterator": { @@ -1348,9 +1348,9 @@ "resolved": "http://registry.npm.taobao.org/es6-iterator/download/es6-iterator-2.0.3.tgz", "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", "requires": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" + "d": "1.0.0", + "es5-ext": "0.10.46", + "es6-symbol": "3.1.1" } }, "es6-symbol": { @@ -1358,8 +1358,8 @@ "resolved": "http://registry.npm.taobao.org/es6-symbol/download/es6-symbol-3.1.1.tgz", "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", "requires": { - "d": "1", - "es5-ext": "~0.10.14" + "d": "1.0.0", + "es5-ext": "0.10.46" } }, "es6-weak-map": { @@ -1367,10 +1367,10 @@ "resolved": "http://registry.npm.taobao.org/es6-weak-map/download/es6-weak-map-2.0.2.tgz", "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", "requires": { - "d": "1", - "es5-ext": "^0.10.14", - "es6-iterator": "^2.0.1", - "es6-symbol": "^3.1.1" + "d": "1.0.0", + "es5-ext": "0.10.46", + "es6-iterator": "2.0.3", + "es6-symbol": "3.1.1" } }, "escape-string-regexp": { @@ -1383,11 +1383,11 @@ "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.11.0.tgz", "integrity": "sha512-IeMV45ReixHS53K/OmfKAIztN/igDHzTJUhZM3k1jMhIZWjk45SMwAtBsEXiJp3vSPmTcu6CXn7mDvFHRN66fw==", "requires": { - "esprima": "^3.1.3", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" + "esprima": "3.1.3", + "estraverse": "4.2.0", + "esutils": "2.0.2", + "optionator": "0.8.2", + "source-map": "0.6.1" } }, "esprima": { @@ -1410,13 +1410,13 @@ "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", "requires": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" + "cross-spawn": "5.1.0", + "get-stream": "3.0.0", + "is-stream": "1.1.0", + "npm-run-path": "2.0.2", + "p-finally": "1.0.0", + "signal-exit": "3.0.2", + "strip-eof": "1.0.0" } }, "expand-brackets": { @@ -1424,13 +1424,13 @@ "resolved": "http://registry.npm.taobao.org/expand-brackets/download/expand-brackets-2.1.4.tgz", "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "posix-character-classes": "0.1.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" }, "dependencies": { "define-property": { @@ -1438,7 +1438,7 @@ "resolved": "http://registry.npm.taobao.org/define-property/download/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "requires": { - "is-descriptor": "^0.1.0" + "is-descriptor": "0.1.6" } }, "extend-shallow": { @@ -1446,7 +1446,7 @@ "resolved": "http://registry.npm.taobao.org/extend-shallow/download/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -1456,7 +1456,7 @@ "resolved": "http://registry.npm.taobao.org/expand-tilde/download/expand-tilde-2.0.2.tgz", "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", "requires": { - "homedir-polyfill": "^1.0.1" + "homedir-polyfill": "1.0.1" } }, "extend": { @@ -1469,8 +1469,8 @@ "resolved": "http://registry.npm.taobao.org/extend-shallow/download/extend-shallow-3.0.2.tgz", "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" + "assign-symbols": "1.0.0", + "is-extendable": "1.0.1" }, "dependencies": { "is-extendable": { @@ -1478,7 +1478,7 @@ "resolved": "http://registry.npm.taobao.org/is-extendable/download/is-extendable-1.0.1.tgz", "integrity": "sha1-p0cPnkJnM9gb2B4RVSZOOjUHyrQ=", "requires": { - "is-plain-object": "^2.0.4" + "is-plain-object": "2.0.4" } } } @@ -1488,14 +1488,14 @@ "resolved": "http://registry.npm.taobao.org/extglob/download/extglob-2.0.4.tgz", "integrity": "sha1-rQD+TcYSqSMuhxhxHcXLWrAoVUM=", "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "array-unique": "0.3.2", + "define-property": "1.0.0", + "expand-brackets": "2.1.4", + "extend-shallow": "2.0.1", + "fragment-cache": "0.2.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" }, "dependencies": { "define-property": { @@ -1503,7 +1503,7 @@ "resolved": "http://registry.npm.taobao.org/define-property/download/define-property-1.0.0.tgz", "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "requires": { - "is-descriptor": "^1.0.0" + "is-descriptor": "1.0.2" } }, "extend-shallow": { @@ -1511,7 +1511,7 @@ "resolved": "http://registry.npm.taobao.org/extend-shallow/download/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } }, "is-accessor-descriptor": { @@ -1519,7 +1519,7 @@ "resolved": "http://registry.npm.taobao.org/is-accessor-descriptor/download/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.2" } }, "is-data-descriptor": { @@ -1527,7 +1527,7 @@ "resolved": "http://registry.npm.taobao.org/is-data-descriptor/download/is-data-descriptor-1.0.0.tgz", "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.2" } }, "is-descriptor": { @@ -1535,9 +1535,9 @@ "resolved": "http://registry.npm.taobao.org/is-descriptor/download/is-descriptor-1.0.2.tgz", "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" } } } @@ -1552,10 +1552,10 @@ "resolved": "http://registry.npm.taobao.org/fancy-log/download/fancy-log-1.3.3.tgz", "integrity": "sha1-28GRVPVYaQFQojlToK29A1vkX8c=", "requires": { - "ansi-gray": "^0.1.1", - "color-support": "^1.1.3", - "parse-node-version": "^1.0.0", - "time-stamp": "^1.0.0" + "ansi-gray": "0.1.1", + "color-support": "1.1.3", + "parse-node-version": "1.0.0", + "time-stamp": "1.1.0" } }, "fast-deep-equal": { @@ -1578,10 +1578,10 @@ "resolved": "http://registry.npm.taobao.org/fill-range/download/fill-range-4.0.0.tgz", "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" }, "dependencies": { "extend-shallow": { @@ -1589,7 +1589,7 @@ "resolved": "http://registry.npm.taobao.org/extend-shallow/download/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -1599,7 +1599,7 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "requires": { - "locate-path": "^2.0.0" + "locate-path": "2.0.0" } }, "findup-sync": { @@ -1607,10 +1607,10 @@ "resolved": "http://registry.npm.taobao.org/findup-sync/download/findup-sync-2.0.0.tgz", "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=", "requires": { - "detect-file": "^1.0.0", - "is-glob": "^3.1.0", - "micromatch": "^3.0.4", - "resolve-dir": "^1.0.1" + "detect-file": "1.0.0", + "is-glob": "3.1.0", + "micromatch": "3.1.10", + "resolve-dir": "1.0.1" }, "dependencies": { "is-glob": { @@ -1618,7 +1618,7 @@ "resolved": "http://registry.npm.taobao.org/is-glob/download/is-glob-3.1.0.tgz", "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", "requires": { - "is-extglob": "^2.1.0" + "is-extglob": "2.1.1" } } } @@ -1628,11 +1628,11 @@ "resolved": "http://registry.npm.taobao.org/fined/download/fined-1.1.1.tgz", "integrity": "sha1-ldiP8ykSPdGmlQ/fzTIfdGJx4B8=", "requires": { - "expand-tilde": "^2.0.2", - "is-plain-object": "^2.0.3", - "object.defaults": "^1.1.0", - "object.pick": "^1.2.0", - "parse-filepath": "^1.0.1" + "expand-tilde": "2.0.2", + "is-plain-object": "2.0.4", + "object.defaults": "1.1.0", + "object.pick": "1.3.0", + "parse-filepath": "1.0.2" } }, "flagged-respawn": { @@ -1645,8 +1645,8 @@ "resolved": "http://registry.npm.taobao.org/flush-write-stream/download/flush-write-stream-1.0.3.tgz", "integrity": "sha1-xdWG7zivYJdlC0m8QbVfq7GfNb0=", "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.4" + "inherits": "2.0.3", + "readable-stream": "2.3.6" } }, "for-in": { @@ -1659,7 +1659,7 @@ "resolved": "http://registry.npm.taobao.org/for-own/download/for-own-1.0.0.tgz", "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", "requires": { - "for-in": "^1.0.1" + "for-in": "1.0.2" } }, "forever-agent": { @@ -1672,9 +1672,9 @@ "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", "requires": { - "asynckit": "^0.4.0", + "asynckit": "0.4.0", "combined-stream": "1.0.6", - "mime-types": "^2.1.12" + "mime-types": "2.1.20" } }, "fragment-cache": { @@ -1682,7 +1682,7 @@ "resolved": "http://registry.npm.taobao.org/fragment-cache/download/fragment-cache-0.2.1.tgz", "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", "requires": { - "map-cache": "^0.2.2" + "map-cache": "0.2.2" } }, "fs-mkdirp-stream": { @@ -1690,8 +1690,8 @@ "resolved": "http://registry.npm.taobao.org/fs-mkdirp-stream/download/fs-mkdirp-stream-1.0.0.tgz", "integrity": "sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes=", "requires": { - "graceful-fs": "^4.1.11", - "through2": "^2.0.3" + "graceful-fs": "4.1.15", + "through2": "2.0.5" } }, "fs.realpath": { @@ -1705,8 +1705,8 @@ "integrity": "sha1-9B3LGvJYKvNpLaNvxVy9jhBBxCY=", "optional": true, "requires": { - "nan": "^2.9.2", - "node-pre-gyp": "^0.10.0" + "nan": "2.12.1", + "node-pre-gyp": "0.10.0" }, "dependencies": { "abbrev": { @@ -2186,7 +2186,7 @@ "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", "requires": { - "assert-plus": "^1.0.0" + "assert-plus": "1.0.0" } }, "glob": { @@ -2194,12 +2194,12 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, "glob-parent": { @@ -2207,8 +2207,8 @@ "resolved": "http://registry.npm.taobao.org/glob-parent/download/glob-parent-3.1.0.tgz", "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" + "is-glob": "3.1.0", + "path-dirname": "1.0.2" }, "dependencies": { "is-glob": { @@ -2216,7 +2216,7 @@ "resolved": "http://registry.npm.taobao.org/is-glob/download/is-glob-3.1.0.tgz", "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", "requires": { - "is-extglob": "^2.1.0" + "is-extglob": "2.1.1" } } } @@ -2226,16 +2226,16 @@ "resolved": "http://registry.npm.taobao.org/glob-stream/download/glob-stream-6.1.0.tgz", "integrity": "sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=", "requires": { - "extend": "^3.0.0", - "glob": "^7.1.1", - "glob-parent": "^3.1.0", - "is-negated-glob": "^1.0.0", - "ordered-read-streams": "^1.0.0", - "pumpify": "^1.3.5", - "readable-stream": "^2.1.5", - "remove-trailing-separator": "^1.0.1", - "to-absolute-glob": "^2.0.0", - "unique-stream": "^2.0.2" + "extend": "3.0.2", + "glob": "7.1.3", + "glob-parent": "3.1.0", + "is-negated-glob": "1.0.0", + "ordered-read-streams": "1.0.1", + "pumpify": "1.5.1", + "readable-stream": "2.3.6", + "remove-trailing-separator": "1.1.0", + "to-absolute-glob": "2.0.2", + "unique-stream": "2.3.1" } }, "glob-watcher": { @@ -2243,12 +2243,12 @@ "resolved": "http://registry.npm.taobao.org/glob-watcher/download/glob-watcher-5.0.3.tgz", "integrity": "sha1-iKir8cTRMeuTkomUvEpZPC5d1iY=", "requires": { - "anymatch": "^2.0.0", - "async-done": "^1.2.0", - "chokidar": "^2.0.0", - "is-negated-glob": "^1.0.0", - "just-debounce": "^1.0.0", - "object.defaults": "^1.1.0" + "anymatch": "2.0.0", + "async-done": "1.3.1", + "chokidar": "2.0.4", + "is-negated-glob": "1.0.0", + "just-debounce": "1.0.0", + "object.defaults": "1.1.0" } }, "global-modules": { @@ -2256,9 +2256,9 @@ "resolved": "http://registry.npm.taobao.org/global-modules/download/global-modules-1.0.0.tgz", "integrity": "sha1-bXcPDrUjrHgWTXK15xqIdyZcw+o=", "requires": { - "global-prefix": "^1.0.1", - "is-windows": "^1.0.1", - "resolve-dir": "^1.0.0" + "global-prefix": "1.0.2", + "is-windows": "1.0.2", + "resolve-dir": "1.0.1" } }, "global-prefix": { @@ -2266,11 +2266,11 @@ "resolved": "http://registry.npm.taobao.org/global-prefix/download/global-prefix-1.0.2.tgz", "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", "requires": { - "expand-tilde": "^2.0.2", - "homedir-polyfill": "^1.0.1", - "ini": "^1.3.4", - "is-windows": "^1.0.1", - "which": "^1.2.14" + "expand-tilde": "2.0.2", + "homedir-polyfill": "1.0.1", + "ini": "1.3.5", + "is-windows": "1.0.2", + "which": "1.3.1" } }, "glogg": { @@ -2278,7 +2278,7 @@ "resolved": "http://registry.npm.taobao.org/glogg/download/glogg-1.0.2.tgz", "integrity": "sha1-LX3XAr7aIus7/634gGltpthGMT8=", "requires": { - "sparkles": "^1.0.0" + "sparkles": "1.0.1" } }, "graceful-fs": { @@ -2291,10 +2291,10 @@ "resolved": "http://registry.npm.taobao.org/gulp/download/gulp-4.0.0.tgz", "integrity": "sha1-lXZsYB2t5Kd+0+eyttwDiBtZY2Y=", "requires": { - "glob-watcher": "^5.0.0", - "gulp-cli": "^2.0.0", - "undertaker": "^1.0.0", - "vinyl-fs": "^3.0.0" + "glob-watcher": "5.0.3", + "gulp-cli": "2.0.1", + "undertaker": "1.2.0", + "vinyl-fs": "3.0.3" }, "dependencies": { "ansi-regex": { @@ -2312,9 +2312,9 @@ "resolved": "http://registry.npm.taobao.org/cliui/download/cliui-3.2.0.tgz", "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wrap-ansi": "^2.0.0" + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wrap-ansi": "2.1.0" } }, "gulp-cli": { @@ -2322,24 +2322,24 @@ "resolved": "http://registry.npm.taobao.org/gulp-cli/download/gulp-cli-2.0.1.tgz", "integrity": "sha1-eEfiIMs2YvK+im1XK/FOF75amUs=", "requires": { - "ansi-colors": "^1.0.1", - "archy": "^1.0.0", - "array-sort": "^1.0.0", - "color-support": "^1.1.3", - "concat-stream": "^1.6.0", - "copy-props": "^2.0.1", - "fancy-log": "^1.3.2", - "gulplog": "^1.0.0", - "interpret": "^1.1.0", - "isobject": "^3.0.1", - "liftoff": "^2.5.0", - "matchdep": "^2.0.0", - "mute-stdout": "^1.0.0", - "pretty-hrtime": "^1.0.0", - "replace-homedir": "^1.0.0", - "semver-greatest-satisfied-range": "^1.1.0", - "v8flags": "^3.0.1", - "yargs": "^7.1.0" + "ansi-colors": "1.1.0", + "archy": "1.0.0", + "array-sort": "1.0.0", + "color-support": "1.1.3", + "concat-stream": "1.6.2", + "copy-props": "2.0.4", + "fancy-log": "1.3.3", + "gulplog": "1.0.0", + "interpret": "1.1.0", + "isobject": "3.0.1", + "liftoff": "2.5.0", + "matchdep": "2.0.0", + "mute-stdout": "1.0.1", + "pretty-hrtime": "1.0.3", + "replace-homedir": "1.0.0", + "semver-greatest-satisfied-range": "1.1.0", + "v8flags": "3.1.2", + "yargs": "7.1.0" } }, "is-fullwidth-code-point": { @@ -2347,7 +2347,7 @@ "resolved": "http://registry.npm.taobao.org/is-fullwidth-code-point/download/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "requires": { - "number-is-nan": "^1.0.0" + "number-is-nan": "1.0.1" } }, "os-locale": { @@ -2355,7 +2355,7 @@ "resolved": "http://registry.npm.taobao.org/os-locale/download/os-locale-1.4.0.tgz", "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", "requires": { - "lcid": "^1.0.0" + "lcid": "1.0.0" } }, "string-width": { @@ -2363,9 +2363,9 @@ "resolved": "http://registry.npm.taobao.org/string-width/download/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" } }, "strip-ansi": { @@ -2373,7 +2373,7 @@ "resolved": "http://registry.npm.taobao.org/strip-ansi/download/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "2.1.1" } }, "which-module": { @@ -2386,19 +2386,19 @@ "resolved": "http://registry.npm.taobao.org/yargs/download/yargs-7.1.0.tgz", "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=", "requires": { - "camelcase": "^3.0.0", - "cliui": "^3.2.0", - "decamelize": "^1.1.1", - "get-caller-file": "^1.0.1", - "os-locale": "^1.4.0", - "read-pkg-up": "^1.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^1.0.2", - "which-module": "^1.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^5.0.0" + "camelcase": "3.0.0", + "cliui": "3.2.0", + "decamelize": "1.2.0", + "get-caller-file": "1.0.3", + "os-locale": "1.4.0", + "read-pkg-up": "1.0.1", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "1.0.2", + "which-module": "1.0.0", + "y18n": "3.2.1", + "yargs-parser": "5.0.0" } }, "yargs-parser": { @@ -2406,7 +2406,7 @@ "resolved": "http://registry.npm.taobao.org/yargs-parser/download/yargs-parser-5.0.0.tgz", "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=", "requires": { - "camelcase": "^3.0.0" + "camelcase": "3.0.0" } } } @@ -2416,9 +2416,9 @@ "resolved": "http://registry.npm.taobao.org/gulp-htmlmin/download/gulp-htmlmin-5.0.1.tgz", "integrity": "sha1-kPxeitBCWp6G1dUhQnGE5ydjZec=", "requires": { - "html-minifier": "^3.5.20", - "plugin-error": "^1.0.1", - "through2": "^2.0.3" + "html-minifier": "3.5.21", + "plugin-error": "1.0.1", + "through2": "2.0.5" } }, "gulp-postcss": { @@ -2426,11 +2426,11 @@ "resolved": "http://registry.npm.taobao.org/gulp-postcss/download/gulp-postcss-8.0.0.tgz", "integrity": "sha1-jTdyzU0nvKVeyMtMjlduO95NxVA=", "requires": { - "fancy-log": "^1.3.2", - "plugin-error": "^1.0.1", - "postcss": "^7.0.2", - "postcss-load-config": "^2.0.0", - "vinyl-sourcemaps-apply": "^0.2.1" + "fancy-log": "1.3.3", + "plugin-error": "1.0.1", + "postcss": "7.0.7", + "postcss-load-config": "2.0.0", + "vinyl-sourcemaps-apply": "0.2.1" } }, "gulp-purifycss": { @@ -2438,10 +2438,10 @@ "resolved": "http://registry.npm.taobao.org/gulp-purifycss/download/gulp-purifycss-0.2.0.tgz", "integrity": "sha1-GCxLrejsmXip+BNmPmkhr8GjUKM=", "requires": { - "glob": "^5.0.10", - "gulp-util": "^3.0.5", - "purify-css": "^1.0.8", - "through2": "^2.0.0" + "glob": "5.0.15", + "gulp-util": "3.0.8", + "purify-css": "1.2.5", + "through2": "2.0.5" }, "dependencies": { "glob": { @@ -2449,11 +2449,11 @@ "resolved": "http://registry.npm.taobao.org/glob/download/glob-5.0.15.tgz", "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } } } @@ -2463,24 +2463,24 @@ "resolved": "http://registry.npm.taobao.org/gulp-util/download/gulp-util-3.0.8.tgz", "integrity": "sha1-AFTh50RQLifATBh8PsxQXdVLu08=", "requires": { - "array-differ": "^1.0.0", - "array-uniq": "^1.0.2", - "beeper": "^1.0.0", - "chalk": "^1.0.0", - "dateformat": "^2.0.0", - "fancy-log": "^1.1.0", - "gulplog": "^1.0.0", - "has-gulplog": "^0.1.0", - "lodash._reescape": "^3.0.0", - "lodash._reevaluate": "^3.0.0", - "lodash._reinterpolate": "^3.0.0", - "lodash.template": "^3.0.0", - "minimist": "^1.1.0", - "multipipe": "^0.1.2", - "object-assign": "^3.0.0", + "array-differ": "1.0.0", + "array-uniq": "1.0.3", + "beeper": "1.1.1", + "chalk": "1.1.3", + "dateformat": "2.2.0", + "fancy-log": "1.3.3", + "gulplog": "1.0.0", + "has-gulplog": "0.1.0", + "lodash._reescape": "3.0.0", + "lodash._reevaluate": "3.0.0", + "lodash._reinterpolate": "3.0.0", + "lodash.template": "3.6.2", + "minimist": "1.2.0", + "multipipe": "0.1.2", + "object-assign": "3.0.0", "replace-ext": "0.0.1", - "through2": "^2.0.0", - "vinyl": "^0.5.0" + "through2": "2.0.5", + "vinyl": "0.5.3" }, "dependencies": { "clone": { @@ -2503,8 +2503,8 @@ "resolved": "http://registry.npm.taobao.org/vinyl/download/vinyl-0.5.3.tgz", "integrity": "sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=", "requires": { - "clone": "^1.0.0", - "clone-stats": "^0.0.1", + "clone": "1.0.4", + "clone-stats": "0.0.1", "replace-ext": "0.0.1" } } @@ -2515,7 +2515,7 @@ "resolved": "http://registry.npm.taobao.org/gulplog/download/gulplog-1.0.0.tgz", "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", "requires": { - "glogg": "^1.0.0" + "glogg": "1.0.2" } }, "har-schema": { @@ -2528,8 +2528,8 @@ "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.0.tgz", "integrity": "sha512-+qnmNjI4OfH2ipQ9VQOw23bBd/ibtfbVdK2fYbY4acTDqKTW/YDp9McimZdDbG8iV9fZizUqQMD5xvriB146TA==", "requires": { - "ajv": "^5.3.0", - "har-schema": "^2.0.0" + "ajv": "5.5.2", + "har-schema": "2.0.0" } }, "has": { @@ -2537,7 +2537,7 @@ "resolved": "http://registry.npm.taobao.org/has/download/has-1.0.3.tgz", "integrity": "sha1-ci18v8H2qoJB8W3YFOAR4fQeh5Y=", "requires": { - "function-bind": "^1.1.1" + "function-bind": "1.1.1" } }, "has-ansi": { @@ -2545,7 +2545,7 @@ "resolved": "http://registry.npm.taobao.org/has-ansi/download/has-ansi-2.0.0.tgz", "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "2.1.1" }, "dependencies": { "ansi-regex": { @@ -2565,7 +2565,7 @@ "resolved": "http://registry.npm.taobao.org/has-gulplog/download/has-gulplog-0.1.0.tgz", "integrity": "sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4=", "requires": { - "sparkles": "^1.0.0" + "sparkles": "1.0.1" } }, "has-symbols": { @@ -2578,9 +2578,9 @@ "resolved": "http://registry.npm.taobao.org/has-value/download/has-value-1.0.0.tgz", "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" + "get-value": "2.0.6", + "has-values": "1.0.0", + "isobject": "3.0.1" } }, "has-values": { @@ -2588,8 +2588,8 @@ "resolved": "http://registry.npm.taobao.org/has-values/download/has-values-1.0.0.tgz", "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" + "is-number": "3.0.0", + "kind-of": "4.0.0" }, "dependencies": { "kind-of": { @@ -2597,7 +2597,7 @@ "resolved": "http://registry.npm.taobao.org/kind-of/download/kind-of-4.0.0.tgz", "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } @@ -2617,7 +2617,7 @@ "resolved": "http://registry.npm.taobao.org/homedir-polyfill/download/homedir-polyfill-1.0.1.tgz", "integrity": "sha1-TCu8inWJmP7r9e1oWA921GdotLw=", "requires": { - "parse-passwd": "^1.0.0" + "parse-passwd": "1.0.0" } }, "hosted-git-info": { @@ -2645,7 +2645,7 @@ "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", "requires": { - "whatwg-encoding": "^1.0.1" + "whatwg-encoding": "1.0.4" } }, "html-minifier": { @@ -2653,13 +2653,13 @@ "resolved": "http://registry.npm.taobao.org/html-minifier/download/html-minifier-3.5.21.tgz", "integrity": "sha1-0AQOBUcw41TbAIRjWTGUAVIS0gw=", "requires": { - "camel-case": "3.0.x", - "clean-css": "4.2.x", - "commander": "2.17.x", - "he": "1.2.x", - "param-case": "2.1.x", - "relateurl": "0.2.x", - "uglify-js": "3.4.x" + "camel-case": "3.0.0", + "clean-css": "4.2.1", + "commander": "2.17.1", + "he": "1.2.0", + "param-case": "2.1.1", + "relateurl": "0.2.7", + "uglify-js": "3.4.9" } }, "http-signature": { @@ -2667,9 +2667,9 @@ "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" + "assert-plus": "1.0.0", + "jsprim": "1.4.1", + "sshpk": "1.14.2" } }, "iconv-lite": { @@ -2677,7 +2677,7 @@ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", "requires": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": "2.1.2" } }, "import-cwd": { @@ -2685,7 +2685,7 @@ "resolved": "http://registry.npm.taobao.org/import-cwd/download/import-cwd-2.1.0.tgz", "integrity": "sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=", "requires": { - "import-from": "^2.1.0" + "import-from": "2.1.0" } }, "import-fresh": { @@ -2693,8 +2693,8 @@ "resolved": "http://registry.npm.taobao.org/import-fresh/download/import-fresh-2.0.0.tgz", "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", "requires": { - "caller-path": "^2.0.0", - "resolve-from": "^3.0.0" + "caller-path": "2.0.0", + "resolve-from": "3.0.0" } }, "import-from": { @@ -2702,7 +2702,7 @@ "resolved": "http://registry.npm.taobao.org/import-from/download/import-from-2.1.0.tgz", "integrity": "sha1-M1238qev/VOqpHHUuAId7ja387E=", "requires": { - "resolve-from": "^3.0.0" + "resolve-from": "3.0.0" } }, "indexes-of": { @@ -2715,8 +2715,8 @@ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "requires": { - "once": "^1.3.0", - "wrappy": "1" + "once": "1.4.0", + "wrappy": "1.0.2" } }, "inherits": { @@ -2749,8 +2749,8 @@ "resolved": "http://registry.npm.taobao.org/is-absolute/download/is-absolute-1.0.0.tgz", "integrity": "sha1-OV4a6EsR8mrReV5zwXN45IowFXY=", "requires": { - "is-relative": "^1.0.0", - "is-windows": "^1.0.1" + "is-relative": "1.0.0", + "is-windows": "1.0.2" } }, "is-absolute-url": { @@ -2763,7 +2763,7 @@ "resolved": "http://registry.npm.taobao.org/is-accessor-descriptor/download/is-accessor-descriptor-0.1.6.tgz", "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -2771,7 +2771,7 @@ "resolved": "http://registry.npm.taobao.org/kind-of/download/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } @@ -2786,7 +2786,7 @@ "resolved": "http://registry.npm.taobao.org/is-binary-path/download/is-binary-path-1.0.1.tgz", "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", "requires": { - "binary-extensions": "^1.0.0" + "binary-extensions": "1.12.0" } }, "is-buffer": { @@ -2799,7 +2799,7 @@ "resolved": "http://registry.npm.taobao.org/is-builtin-module/download/is-builtin-module-1.0.0.tgz", "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", "requires": { - "builtin-modules": "^1.0.0" + "builtin-modules": "1.1.1" } }, "is-callable": { @@ -2812,12 +2812,12 @@ "resolved": "http://registry.npm.taobao.org/is-color-stop/download/is-color-stop-1.1.0.tgz", "integrity": "sha1-z/9HGu5N1cnhWFmPvhKWe1za00U=", "requires": { - "css-color-names": "^0.0.4", - "hex-color-regex": "^1.1.0", - "hsl-regex": "^1.0.0", - "hsla-regex": "^1.0.0", - "rgb-regex": "^1.0.1", - "rgba-regex": "^1.0.0" + "css-color-names": "0.0.4", + "hex-color-regex": "1.1.0", + "hsl-regex": "1.0.0", + "hsla-regex": "1.0.0", + "rgb-regex": "1.0.1", + "rgba-regex": "1.0.0" } }, "is-data-descriptor": { @@ -2825,7 +2825,7 @@ "resolved": "http://registry.npm.taobao.org/is-data-descriptor/download/is-data-descriptor-0.1.4.tgz", "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -2833,7 +2833,7 @@ "resolved": "http://registry.npm.taobao.org/kind-of/download/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } @@ -2848,9 +2848,9 @@ "resolved": "http://registry.npm.taobao.org/is-descriptor/download/is-descriptor-0.1.6.tgz", "integrity": "sha1-Nm2CQN3kh8pRgjsaufB6EKeCUco=", "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" }, "dependencies": { "kind-of": { @@ -2885,7 +2885,7 @@ "resolved": "http://registry.npm.taobao.org/is-glob/download/is-glob-4.0.0.tgz", "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", "requires": { - "is-extglob": "^2.1.1" + "is-extglob": "2.1.1" } }, "is-negated-glob": { @@ -2898,7 +2898,7 @@ "resolved": "http://registry.npm.taobao.org/is-number/download/is-number-3.0.0.tgz", "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -2906,7 +2906,7 @@ "resolved": "http://registry.npm.taobao.org/kind-of/download/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } @@ -2921,7 +2921,7 @@ "resolved": "http://registry.npm.taobao.org/is-plain-object/download/is-plain-object-2.0.4.tgz", "integrity": "sha1-LBY7P6+xtgbZ0Xko8FwqHDjgdnc=", "requires": { - "isobject": "^3.0.1" + "isobject": "3.0.1" } }, "is-regex": { @@ -2929,7 +2929,7 @@ "resolved": "http://registry.npm.taobao.org/is-regex/download/is-regex-1.0.4.tgz", "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", "requires": { - "has": "^1.0.1" + "has": "1.0.3" } }, "is-relative": { @@ -2937,7 +2937,7 @@ "resolved": "http://registry.npm.taobao.org/is-relative/download/is-relative-1.0.0.tgz", "integrity": "sha1-obtpNc6MXboei5dUubLcwCDiJg0=", "requires": { - "is-unc-path": "^1.0.0" + "is-unc-path": "1.0.0" } }, "is-resolvable": { @@ -2955,7 +2955,7 @@ "resolved": "http://registry.npm.taobao.org/is-svg/download/is-svg-3.0.0.tgz", "integrity": "sha1-kyHb0pwhLlypnE+peUxxS8r6L3U=", "requires": { - "html-comment-regex": "^1.1.0" + "html-comment-regex": "1.1.2" } }, "is-symbol": { @@ -2963,7 +2963,7 @@ "resolved": "http://registry.npm.taobao.org/is-symbol/download/is-symbol-1.0.2.tgz", "integrity": "sha1-oFX2rlcZLK7jKeeoYBGLSXqVDzg=", "requires": { - "has-symbols": "^1.0.0" + "has-symbols": "1.0.0" } }, "is-typedarray": { @@ -2976,7 +2976,7 @@ "resolved": "http://registry.npm.taobao.org/is-unc-path/download/is-unc-path-1.0.0.tgz", "integrity": "sha1-1zHoiY7QkKEsNSrS6u1Qla0yLJ0=", "requires": { - "unc-path-regex": "^0.1.2" + "unc-path-regex": "0.1.2" } }, "is-utf8": { @@ -3019,8 +3019,8 @@ "resolved": "http://registry.npm.taobao.org/js-yaml/download/js-yaml-3.12.0.tgz", "integrity": "sha1-6u1lbsg0TxD1J8a/obbiJE3hZ9E=", "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "1.0.10", + "esprima": "4.0.1" }, "dependencies": { "esprima": { @@ -3041,32 +3041,32 @@ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==", "requires": { - "abab": "^2.0.0", - "acorn": "^5.5.3", - "acorn-globals": "^4.1.0", - "array-equal": "^1.0.0", - "cssom": ">= 0.3.2 < 0.4.0", - "cssstyle": "^1.0.0", - "data-urls": "^1.0.0", - "domexception": "^1.0.1", - "escodegen": "^1.9.1", - "html-encoding-sniffer": "^1.0.2", - "left-pad": "^1.3.0", - "nwsapi": "^2.0.7", + "abab": "2.0.0", + "acorn": "5.7.2", + "acorn-globals": "4.1.0", + "array-equal": "1.0.0", + "cssom": "0.3.4", + "cssstyle": "1.1.1", + "data-urls": "1.0.1", + "domexception": "1.0.1", + "escodegen": "1.11.0", + "html-encoding-sniffer": "1.0.2", + "left-pad": "1.3.0", + "nwsapi": "2.0.9", "parse5": "4.0.0", - "pn": "^1.1.0", - "request": "^2.87.0", - "request-promise-native": "^1.0.5", - "sax": "^1.2.4", - "symbol-tree": "^3.2.2", - "tough-cookie": "^2.3.4", - "w3c-hr-time": "^1.0.1", - "webidl-conversions": "^4.0.2", - "whatwg-encoding": "^1.0.3", - "whatwg-mimetype": "^2.1.0", - "whatwg-url": "^6.4.1", - "ws": "^5.2.0", - "xml-name-validator": "^3.0.0" + "pn": "1.1.0", + "request": "2.88.0", + "request-promise-native": "1.0.5", + "sax": "1.2.4", + "symbol-tree": "3.2.2", + "tough-cookie": "2.4.3", + "w3c-hr-time": "1.0.1", + "webidl-conversions": "4.0.2", + "whatwg-encoding": "1.0.4", + "whatwg-mimetype": "2.1.0", + "whatwg-url": "6.5.0", + "ws": "5.2.2", + "xml-name-validator": "3.0.0" } }, "json-parse-better-errors": { @@ -3120,8 +3120,8 @@ "resolved": "http://registry.npm.taobao.org/last-run/download/last-run-1.1.1.tgz", "integrity": "sha1-RblpQsF7HHnHchmCWbqUO+v4yls=", "requires": { - "default-resolution": "^2.0.0", - "es6-weak-map": "^2.0.1" + "default-resolution": "2.0.0", + "es6-weak-map": "2.0.2" } }, "lazystream": { @@ -3129,7 +3129,7 @@ "resolved": "http://registry.npm.taobao.org/lazystream/download/lazystream-1.0.0.tgz", "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", "requires": { - "readable-stream": "^2.0.5" + "readable-stream": "2.3.6" } }, "lcid": { @@ -3137,7 +3137,7 @@ "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", "requires": { - "invert-kv": "^1.0.0" + "invert-kv": "1.0.0" } }, "lead": { @@ -3145,7 +3145,7 @@ "resolved": "http://registry.npm.taobao.org/lead/download/lead-1.0.0.tgz", "integrity": "sha1-bxT5mje+Op3XhPVJVpDlkDRm7kI=", "requires": { - "flush-write-stream": "^1.0.2" + "flush-write-stream": "1.0.3" } }, "left-pad": { @@ -3158,8 +3158,8 @@ "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" + "prelude-ls": "1.1.2", + "type-check": "0.3.2" } }, "liftoff": { @@ -3167,14 +3167,14 @@ "resolved": "http://registry.npm.taobao.org/liftoff/download/liftoff-2.5.0.tgz", "integrity": "sha1-IAkpG7Mc6oYbvxCnwVooyvdcMew=", "requires": { - "extend": "^3.0.0", - "findup-sync": "^2.0.0", - "fined": "^1.0.1", - "flagged-respawn": "^1.0.0", - "is-plain-object": "^2.0.4", - "object.map": "^1.0.0", - "rechoir": "^0.6.2", - "resolve": "^1.1.7" + "extend": "3.0.2", + "findup-sync": "2.0.0", + "fined": "1.1.1", + "flagged-respawn": "1.0.1", + "is-plain-object": "2.0.4", + "object.map": "1.0.1", + "rechoir": "0.6.2", + "resolve": "1.8.1" } }, "load-json-file": { @@ -3182,11 +3182,11 @@ "resolved": "http://registry.npm.taobao.org/load-json-file/download/load-json-file-1.1.0.tgz", "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" + "graceful-fs": "4.1.15", + "parse-json": "2.2.0", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "strip-bom": "2.0.0" } }, "locate-path": { @@ -3194,8 +3194,8 @@ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" + "p-locate": "2.0.0", + "path-exists": "3.0.0" } }, "lodash": { @@ -3258,7 +3258,7 @@ "resolved": "http://registry.npm.taobao.org/lodash.escape/download/lodash.escape-3.2.0.tgz", "integrity": "sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=", "requires": { - "lodash._root": "^3.0.0" + "lodash._root": "3.0.1" } }, "lodash.isarguments": { @@ -3276,9 +3276,9 @@ "resolved": "http://registry.npm.taobao.org/lodash.keys/download/lodash.keys-3.1.2.tgz", "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", "requires": { - "lodash._getnative": "^3.0.0", - "lodash.isarguments": "^3.0.0", - "lodash.isarray": "^3.0.0" + "lodash._getnative": "3.9.1", + "lodash.isarguments": "3.1.0", + "lodash.isarray": "3.0.4" } }, "lodash.memoize": { @@ -3301,15 +3301,15 @@ "resolved": "http://registry.npm.taobao.org/lodash.template/download/lodash.template-3.6.2.tgz", "integrity": "sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=", "requires": { - "lodash._basecopy": "^3.0.0", - "lodash._basetostring": "^3.0.0", - "lodash._basevalues": "^3.0.0", - "lodash._isiterateecall": "^3.0.0", - "lodash._reinterpolate": "^3.0.0", - "lodash.escape": "^3.0.0", - "lodash.keys": "^3.0.0", - "lodash.restparam": "^3.0.0", - "lodash.templatesettings": "^3.0.0" + "lodash._basecopy": "3.0.1", + "lodash._basetostring": "3.0.1", + "lodash._basevalues": "3.0.0", + "lodash._isiterateecall": "3.0.9", + "lodash._reinterpolate": "3.0.0", + "lodash.escape": "3.2.0", + "lodash.keys": "3.1.2", + "lodash.restparam": "3.6.1", + "lodash.templatesettings": "3.1.1" } }, "lodash.templatesettings": { @@ -3317,8 +3317,8 @@ "resolved": "http://registry.npm.taobao.org/lodash.templatesettings/download/lodash.templatesettings-3.1.1.tgz", "integrity": "sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU=", "requires": { - "lodash._reinterpolate": "^3.0.0", - "lodash.escape": "^3.0.0" + "lodash._reinterpolate": "3.0.0", + "lodash.escape": "3.2.0" } }, "lodash.uniq": { @@ -3336,8 +3336,8 @@ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" + "pseudomap": "1.0.2", + "yallist": "2.1.2" } }, "make-iterator": { @@ -3345,7 +3345,7 @@ "resolved": "http://registry.npm.taobao.org/make-iterator/download/make-iterator-1.0.1.tgz", "integrity": "sha1-KbM/MSqo9UfEpeSQ9Wr87JkTOtY=", "requires": { - "kind-of": "^6.0.2" + "kind-of": "6.0.2" } }, "map-cache": { @@ -3358,7 +3358,7 @@ "resolved": "http://registry.npm.taobao.org/map-visit/download/map-visit-1.0.0.tgz", "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", "requires": { - "object-visit": "^1.0.0" + "object-visit": "1.0.1" } }, "matchdep": { @@ -3366,9 +3366,9 @@ "resolved": "http://registry.npm.taobao.org/matchdep/download/matchdep-2.0.0.tgz", "integrity": "sha1-xvNINKDY28OzfCfui7yyfHd1WC4=", "requires": { - "findup-sync": "^2.0.0", - "micromatch": "^3.0.4", - "resolve": "^1.4.0", + "findup-sync": "2.0.0", + "micromatch": "3.1.10", + "resolve": "1.8.1", "stack-trace": "0.0.10" } }, @@ -3382,9 +3382,9 @@ "resolved": "https://registry.npmjs.org/mathjax-node/-/mathjax-node-2.1.1.tgz", "integrity": "sha1-JcgPSU91QEGP/Pqcx1bf0hUCAb0=", "requires": { - "is-fullwidth-code-point": "^2.0.0", - "jsdom": "^11.0.0", - "mathjax": "^2.7.2" + "is-fullwidth-code-point": "2.0.0", + "jsdom": "11.12.0", + "mathjax": "2.7.5" } }, "mathjax-node-page": { @@ -3392,8 +3392,8 @@ "resolved": "https://registry.npmjs.org/mathjax-node-page/-/mathjax-node-page-3.0.1.tgz", "integrity": "sha512-LlkChMBprwRqiNIhQ6eSuIG/FNt7HUzy0wkm2BD9/guGRLGvYxAmUC+cGbupmmqnuv0S1p0Un04BBoZAWdPqrQ==", "requires": { - "mathjax-node": "^2.0.0", - "yargs": "^11.0.0" + "mathjax-node": "2.1.1", + "yargs": "11.1.0" } }, "mdast-comment-marker": { @@ -3421,7 +3421,7 @@ "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", "requires": { - "mimic-fn": "^1.0.0" + "mimic-fn": "1.2.0" } }, "micromatch": { @@ -3429,19 +3429,19 @@ "resolved": "http://registry.npm.taobao.org/micromatch/download/micromatch-3.1.10.tgz", "integrity": "sha1-cIWbyVyYQJUvNZoGij/En57PrCM=", "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "extglob": "2.0.4", + "fragment-cache": "0.2.1", + "kind-of": "6.0.2", + "nanomatch": "1.2.13", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" } }, "mime-db": { @@ -3454,7 +3454,7 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.20.tgz", "integrity": "sha512-HrkrPaP9vGuWbLK1B1FfgAkbqNjIuy4eHlIYnFi7kamZyLLrGlo2mpcx0bBmNpKqBtYtAfGbodDddIgddSJC2A==", "requires": { - "mime-db": "~1.36.0" + "mime-db": "1.36.0" } }, "mimic-fn": { @@ -3467,7 +3467,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "requires": { - "brace-expansion": "^1.1.7" + "brace-expansion": "1.1.11" } }, "minimist": { @@ -3480,8 +3480,8 @@ "resolved": "http://registry.npm.taobao.org/mixin-deep/download/mixin-deep-1.3.1.tgz", "integrity": "sha1-pJ5yaNzhoNlpjkUybFYm3zVD0P4=", "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" + "for-in": "1.0.2", + "is-extendable": "1.0.1" }, "dependencies": { "is-extendable": { @@ -3489,7 +3489,7 @@ "resolved": "http://registry.npm.taobao.org/is-extendable/download/is-extendable-1.0.1.tgz", "integrity": "sha1-p0cPnkJnM9gb2B4RVSZOOjUHyrQ=", "requires": { - "is-plain-object": "^2.0.4" + "is-plain-object": "2.0.4" } } } @@ -3538,17 +3538,17 @@ "resolved": "http://registry.npm.taobao.org/nanomatch/download/nanomatch-1.2.13.tgz", "integrity": "sha1-uHqKpPwN6P5r6IiVs4mD/yZb0Rk=", "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "fragment-cache": "0.2.1", + "is-windows": "1.0.2", + "kind-of": "6.0.2", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" } }, "next-tick": { @@ -3561,7 +3561,7 @@ "resolved": "http://registry.npm.taobao.org/no-case/download/no-case-2.3.2.tgz", "integrity": "sha1-YLgTOWvjmz8SiKTB7V0efSi0ZKw=", "requires": { - "lower-case": "^1.1.1" + "lower-case": "1.1.4" } }, "node-releases": { @@ -3569,7 +3569,7 @@ "resolved": "http://registry.npm.taobao.org/node-releases/download/node-releases-1.1.2.tgz", "integrity": "sha1-k8F/ul7shlCtkI3lQz+odjuuvk0=", "requires": { - "semver": "^5.3.0" + "semver": "5.6.0" } }, "normalize-package-data": { @@ -3577,10 +3577,10 @@ "resolved": "http://registry.npm.taobao.org/normalize-package-data/download/normalize-package-data-2.4.0.tgz", "integrity": "sha1-EvlaMH1YNSB1oEkHuErIvpisAS8=", "requires": { - "hosted-git-info": "^2.1.4", - "is-builtin-module": "^1.0.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" + "hosted-git-info": "2.7.1", + "is-builtin-module": "1.0.0", + "semver": "5.6.0", + "validate-npm-package-license": "3.0.4" } }, "normalize-path": { @@ -3588,7 +3588,7 @@ "resolved": "http://registry.npm.taobao.org/normalize-path/download/normalize-path-2.1.1.tgz", "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", "requires": { - "remove-trailing-separator": "^1.0.1" + "remove-trailing-separator": "1.1.0" } }, "now-and-later": { @@ -3596,7 +3596,7 @@ "resolved": "http://registry.npm.taobao.org/now-and-later/download/now-and-later-2.0.0.tgz", "integrity": "sha1-vGHLtFbXnLMiB85HygUTb/Ln1u4=", "requires": { - "once": "^1.3.2" + "once": "1.4.0" } }, "npm-run-path": { @@ -3604,7 +3604,7 @@ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", "requires": { - "path-key": "^2.0.0" + "path-key": "2.0.1" } }, "nth-check": { @@ -3612,7 +3612,7 @@ "resolved": "http://registry.npm.taobao.org/nth-check/download/nth-check-1.0.2.tgz", "integrity": "sha1-sr0pXDfj3VijvwcAN2Zjuk2c8Fw=", "requires": { - "boolbase": "~1.0.0" + "boolbase": "1.0.0" } }, "number-is-nan": { @@ -3640,9 +3640,9 @@ "resolved": "http://registry.npm.taobao.org/object-copy/download/object-copy-0.1.0.tgz", "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" + "copy-descriptor": "0.1.1", + "define-property": "0.2.5", + "kind-of": "3.2.2" }, "dependencies": { "define-property": { @@ -3650,7 +3650,7 @@ "resolved": "http://registry.npm.taobao.org/define-property/download/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "requires": { - "is-descriptor": "^0.1.0" + "is-descriptor": "0.1.6" } }, "kind-of": { @@ -3658,7 +3658,7 @@ "resolved": "http://registry.npm.taobao.org/kind-of/download/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } @@ -3673,7 +3673,7 @@ "resolved": "http://registry.npm.taobao.org/object-visit/download/object-visit-1.0.1.tgz", "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", "requires": { - "isobject": "^3.0.0" + "isobject": "3.0.1" } }, "object.assign": { @@ -3681,10 +3681,10 @@ "resolved": "http://registry.npm.taobao.org/object.assign/download/object.assign-4.1.0.tgz", "integrity": "sha1-lovxEA15Vrs8oIbwBvhGs7xACNo=", "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" + "define-properties": "1.1.3", + "function-bind": "1.1.1", + "has-symbols": "1.0.0", + "object-keys": "1.0.12" } }, "object.defaults": { @@ -3692,10 +3692,10 @@ "resolved": "http://registry.npm.taobao.org/object.defaults/download/object.defaults-1.1.0.tgz", "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=", "requires": { - "array-each": "^1.0.1", - "array-slice": "^1.0.0", - "for-own": "^1.0.0", - "isobject": "^3.0.0" + "array-each": "1.0.1", + "array-slice": "1.1.0", + "for-own": "1.0.0", + "isobject": "3.0.1" } }, "object.getownpropertydescriptors": { @@ -3703,8 +3703,8 @@ "resolved": "http://registry.npm.taobao.org/object.getownpropertydescriptors/download/object.getownpropertydescriptors-2.0.3.tgz", "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.5.1" + "define-properties": "1.1.3", + "es-abstract": "1.12.0" } }, "object.map": { @@ -3712,8 +3712,8 @@ "resolved": "http://registry.npm.taobao.org/object.map/download/object.map-1.0.1.tgz", "integrity": "sha1-z4Plncj8wK1fQlDh94s7gb2AHTc=", "requires": { - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" + "for-own": "1.0.0", + "make-iterator": "1.0.1" } }, "object.pick": { @@ -3721,7 +3721,7 @@ "resolved": "http://registry.npm.taobao.org/object.pick/download/object.pick-1.3.0.tgz", "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", "requires": { - "isobject": "^3.0.1" + "isobject": "3.0.1" } }, "object.reduce": { @@ -3729,8 +3729,8 @@ "resolved": "http://registry.npm.taobao.org/object.reduce/download/object.reduce-1.0.1.tgz", "integrity": "sha1-b+NI8qx/oPlcpiEiZZkJaCW7A60=", "requires": { - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" + "for-own": "1.0.0", + "make-iterator": "1.0.1" } }, "object.values": { @@ -3738,10 +3738,10 @@ "resolved": "http://registry.npm.taobao.org/object.values/download/object.values-1.0.4.tgz", "integrity": "sha1-5STaCbT2b/Bd9FdUbscqyZ8TBpo=", "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.6.1", - "function-bind": "^1.1.0", - "has": "^1.0.1" + "define-properties": "1.1.3", + "es-abstract": "1.12.0", + "function-bind": "1.1.1", + "has": "1.0.3" } }, "once": { @@ -3749,7 +3749,7 @@ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "requires": { - "wrappy": "1" + "wrappy": "1.0.2" } }, "optionator": { @@ -3757,12 +3757,12 @@ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.4", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "wordwrap": "~1.0.0" + "deep-is": "0.1.3", + "fast-levenshtein": "2.0.6", + "levn": "0.3.0", + "prelude-ls": "1.1.2", + "type-check": "0.3.2", + "wordwrap": "1.0.0" } }, "ordered-read-streams": { @@ -3770,7 +3770,7 @@ "resolved": "http://registry.npm.taobao.org/ordered-read-streams/download/ordered-read-streams-1.0.1.tgz", "integrity": "sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4=", "requires": { - "readable-stream": "^2.0.1" + "readable-stream": "2.3.6" } }, "os-locale": { @@ -3778,9 +3778,9 @@ "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", "requires": { - "execa": "^0.7.0", - "lcid": "^1.0.0", - "mem": "^1.1.0" + "execa": "0.7.0", + "lcid": "1.0.0", + "mem": "1.1.0" } }, "p-finally": { @@ -3793,7 +3793,7 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", "requires": { - "p-try": "^1.0.0" + "p-try": "1.0.0" } }, "p-locate": { @@ -3801,7 +3801,7 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "requires": { - "p-limit": "^1.1.0" + "p-limit": "1.3.0" } }, "p-try": { @@ -3819,7 +3819,7 @@ "resolved": "http://registry.npm.taobao.org/param-case/download/param-case-2.1.1.tgz", "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=", "requires": { - "no-case": "^2.2.0" + "no-case": "2.3.2" } }, "parse-filepath": { @@ -3827,9 +3827,9 @@ "resolved": "http://registry.npm.taobao.org/parse-filepath/download/parse-filepath-1.0.2.tgz", "integrity": "sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE=", "requires": { - "is-absolute": "^1.0.0", - "map-cache": "^0.2.0", - "path-root": "^0.1.1" + "is-absolute": "1.0.0", + "map-cache": "0.2.2", + "path-root": "0.1.1" } }, "parse-json": { @@ -3837,7 +3837,7 @@ "resolved": "http://registry.npm.taobao.org/parse-json/download/parse-json-2.2.0.tgz", "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "requires": { - "error-ex": "^1.2.0" + "error-ex": "1.3.2" } }, "parse-node-version": { @@ -3890,7 +3890,7 @@ "resolved": "http://registry.npm.taobao.org/path-root/download/path-root-0.1.1.tgz", "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=", "requires": { - "path-root-regex": "^0.1.0" + "path-root-regex": "0.1.2" } }, "path-root-regex": { @@ -3903,9 +3903,9 @@ "resolved": "http://registry.npm.taobao.org/path-type/download/path-type-1.1.0.tgz", "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" + "graceful-fs": "4.1.15", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" } }, "performance-now": { @@ -3928,7 +3928,7 @@ "resolved": "http://registry.npm.taobao.org/pinkie-promise/download/pinkie-promise-2.0.1.tgz", "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", "requires": { - "pinkie": "^2.0.0" + "pinkie": "2.0.4" } }, "plugin-error": { @@ -3936,10 +3936,10 @@ "resolved": "http://registry.npm.taobao.org/plugin-error/download/plugin-error-1.0.1.tgz", "integrity": "sha1-dwFr2JGdCsN3/c3QMiMolTyleBw=", "requires": { - "ansi-colors": "^1.0.1", - "arr-diff": "^4.0.0", - "arr-union": "^3.1.0", - "extend-shallow": "^3.0.2" + "ansi-colors": "1.1.0", + "arr-diff": "4.0.0", + "arr-union": "3.1.0", + "extend-shallow": "3.0.2" } }, "plur": { @@ -3947,7 +3947,7 @@ "resolved": "https://registry.npmjs.org/plur/-/plur-3.0.1.tgz", "integrity": "sha512-lJl0ojUynAM1BZn58Pas2WT/TXeC1+bS+UqShl0x9+49AtOn7DixRXVzaC8qrDOIxNDmepKnLuMTH7NQmkX0PA==", "requires": { - "irregular-plurals": "^2.0.0" + "irregular-plurals": "2.0.0" } }, "pn": { @@ -3965,9 +3965,9 @@ "resolved": "http://registry.npm.taobao.org/postcss/download/postcss-7.0.7.tgz", "integrity": "sha1-J1TQc/d6y07wjxI1w2xXIacgFhQ=", "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.5.0" + "chalk": "2.4.1", + "source-map": "0.6.1", + "supports-color": "5.5.0" }, "dependencies": { "ansi-styles": { @@ -3975,7 +3975,7 @@ "resolved": "http://registry.npm.taobao.org/ansi-styles/download/ansi-styles-3.2.1.tgz", "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", "requires": { - "color-convert": "^1.9.0" + "color-convert": "1.9.3" } }, "chalk": { @@ -3983,9 +3983,9 @@ "resolved": "http://registry.npm.taobao.org/chalk/download/chalk-2.4.1.tgz", "integrity": "sha1-GMSasWoDe26wFSzIPjRxM4IVtm4=", "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.5.0" } }, "supports-color": { @@ -3993,7 +3993,7 @@ "resolved": "http://registry.npm.taobao.org/supports-color/download/supports-color-5.5.0.tgz", "integrity": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", "requires": { - "has-flag": "^3.0.0" + "has-flag": "3.0.0" } } } @@ -4003,10 +4003,10 @@ "resolved": "http://registry.npm.taobao.org/postcss-calc/download/postcss-calc-7.0.1.tgz", "integrity": "sha1-Ntd7qwI7Dsu5eJ2E3LI8SUEUVDY=", "requires": { - "css-unit-converter": "^1.1.1", - "postcss": "^7.0.5", - "postcss-selector-parser": "^5.0.0-rc.4", - "postcss-value-parser": "^3.3.1" + "css-unit-converter": "1.1.1", + "postcss": "7.0.7", + "postcss-selector-parser": "5.0.0", + "postcss-value-parser": "3.3.1" } }, "postcss-colormin": { @@ -4014,11 +4014,11 @@ "resolved": "http://registry.npm.taobao.org/postcss-colormin/download/postcss-colormin-4.0.2.tgz", "integrity": "sha1-k80foRKAAIaWiH2xpSgEixjn7Zk=", "requires": { - "browserslist": "^4.0.0", - "color": "^3.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" + "browserslist": "4.3.6", + "color": "3.1.0", + "has": "1.0.3", + "postcss": "7.0.7", + "postcss-value-parser": "3.3.1" } }, "postcss-convert-values": { @@ -4026,8 +4026,8 @@ "resolved": "http://registry.npm.taobao.org/postcss-convert-values/download/postcss-convert-values-4.0.1.tgz", "integrity": "sha1-yjgT7U2g+BL51DcDWE5Enr4Ymn8=", "requires": { - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" + "postcss": "7.0.7", + "postcss-value-parser": "3.3.1" } }, "postcss-discard-comments": { @@ -4035,7 +4035,7 @@ "resolved": "http://registry.npm.taobao.org/postcss-discard-comments/download/postcss-discard-comments-4.0.1.tgz", "integrity": "sha1-MGl3NbDEdoUqehEFDrhDh6Z+9V0=", "requires": { - "postcss": "^7.0.0" + "postcss": "7.0.7" } }, "postcss-discard-duplicates": { @@ -4043,7 +4043,7 @@ "resolved": "http://registry.npm.taobao.org/postcss-discard-duplicates/download/postcss-discard-duplicates-4.0.2.tgz", "integrity": "sha1-P+EzzTyCKC5VD8myORdqkge3hOs=", "requires": { - "postcss": "^7.0.0" + "postcss": "7.0.7" } }, "postcss-discard-empty": { @@ -4051,7 +4051,7 @@ "resolved": "http://registry.npm.taobao.org/postcss-discard-empty/download/postcss-discard-empty-4.0.1.tgz", "integrity": "sha1-yMlR6fc+2UKAGUWERKAq2Qu592U=", "requires": { - "postcss": "^7.0.0" + "postcss": "7.0.7" } }, "postcss-discard-overridden": { @@ -4059,7 +4059,7 @@ "resolved": "http://registry.npm.taobao.org/postcss-discard-overridden/download/postcss-discard-overridden-4.0.1.tgz", "integrity": "sha1-ZSrvipZybwKfXj4AFG7npOdV/1c=", "requires": { - "postcss": "^7.0.0" + "postcss": "7.0.7" } }, "postcss-load-config": { @@ -4067,8 +4067,8 @@ "resolved": "http://registry.npm.taobao.org/postcss-load-config/download/postcss-load-config-2.0.0.tgz", "integrity": "sha1-8TEt2/WRLNdHF3CDxe96GdYu5IQ=", "requires": { - "cosmiconfig": "^4.0.0", - "import-cwd": "^2.0.0" + "cosmiconfig": "4.0.0", + "import-cwd": "2.1.0" }, "dependencies": { "cosmiconfig": { @@ -4076,10 +4076,10 @@ "resolved": "http://registry.npm.taobao.org/cosmiconfig/download/cosmiconfig-4.0.0.tgz", "integrity": "sha1-dgORVJWAu9LfHlYrwXexPCkJctw=", "requires": { - "is-directory": "^0.3.1", - "js-yaml": "^3.9.0", - "parse-json": "^4.0.0", - "require-from-string": "^2.0.1" + "is-directory": "0.3.1", + "js-yaml": "3.12.0", + "parse-json": "4.0.0", + "require-from-string": "2.0.2" } }, "parse-json": { @@ -4087,8 +4087,8 @@ "resolved": "http://registry.npm.taobao.org/parse-json/download/parse-json-4.0.0.tgz", "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" + "error-ex": "1.3.2", + "json-parse-better-errors": "1.0.2" } } } @@ -4099,9 +4099,9 @@ "integrity": "sha1-xNY6tXvcBUq0BnqwddSIyMKXg4A=", "requires": { "css-color-names": "0.0.4", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0", - "stylehacks": "^4.0.0" + "postcss": "7.0.7", + "postcss-value-parser": "3.3.1", + "stylehacks": "4.0.1" } }, "postcss-merge-rules": { @@ -4109,12 +4109,12 @@ "resolved": "http://registry.npm.taobao.org/postcss-merge-rules/download/postcss-merge-rules-4.0.2.tgz", "integrity": "sha1-K+REAb8ZhW8n8yuLEsDfWvG4jnQ=", "requires": { - "browserslist": "^4.0.0", - "caniuse-api": "^3.0.0", - "cssnano-util-same-parent": "^4.0.0", - "postcss": "^7.0.0", - "postcss-selector-parser": "^3.0.0", - "vendors": "^1.0.0" + "browserslist": "4.3.6", + "caniuse-api": "3.0.0", + "cssnano-util-same-parent": "4.0.1", + "postcss": "7.0.7", + "postcss-selector-parser": "3.1.1", + "vendors": "1.0.2" }, "dependencies": { "postcss-selector-parser": { @@ -4122,9 +4122,9 @@ "resolved": "http://registry.npm.taobao.org/postcss-selector-parser/download/postcss-selector-parser-3.1.1.tgz", "integrity": "sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU=", "requires": { - "dot-prop": "^4.1.1", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" + "dot-prop": "4.2.0", + "indexes-of": "1.0.1", + "uniq": "1.0.1" } } } @@ -4134,8 +4134,8 @@ "resolved": "http://registry.npm.taobao.org/postcss-minify-font-values/download/postcss-minify-font-values-4.0.2.tgz", "integrity": "sha1-zUw0TM5HQ0P6xdgiBqssvLiv1aY=", "requires": { - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" + "postcss": "7.0.7", + "postcss-value-parser": "3.3.1" } }, "postcss-minify-gradients": { @@ -4143,10 +4143,10 @@ "resolved": "http://registry.npm.taobao.org/postcss-minify-gradients/download/postcss-minify-gradients-4.0.1.tgz", "integrity": "sha1-balcbpKoCflWu3a/DARJSVPhp90=", "requires": { - "cssnano-util-get-arguments": "^4.0.0", - "is-color-stop": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" + "cssnano-util-get-arguments": "4.0.0", + "is-color-stop": "1.1.0", + "postcss": "7.0.7", + "postcss-value-parser": "3.3.1" } }, "postcss-minify-params": { @@ -4154,12 +4154,12 @@ "resolved": "http://registry.npm.taobao.org/postcss-minify-params/download/postcss-minify-params-4.0.1.tgz", "integrity": "sha1-Wy4tAmTdZF711o+P7A1MOMHPk9I=", "requires": { - "alphanum-sort": "^1.0.0", - "browserslist": "^4.0.0", - "cssnano-util-get-arguments": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0", - "uniqs": "^2.0.0" + "alphanum-sort": "1.0.2", + "browserslist": "4.3.6", + "cssnano-util-get-arguments": "4.0.0", + "postcss": "7.0.7", + "postcss-value-parser": "3.3.1", + "uniqs": "2.0.0" } }, "postcss-minify-selectors": { @@ -4167,10 +4167,10 @@ "resolved": "http://registry.npm.taobao.org/postcss-minify-selectors/download/postcss-minify-selectors-4.0.1.tgz", "integrity": "sha1-qJHBl5d8w3q/YLPqBrhCSLHB6c0=", "requires": { - "alphanum-sort": "^1.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-selector-parser": "^3.0.0" + "alphanum-sort": "1.0.2", + "has": "1.0.3", + "postcss": "7.0.7", + "postcss-selector-parser": "3.1.1" }, "dependencies": { "postcss-selector-parser": { @@ -4178,9 +4178,9 @@ "resolved": "http://registry.npm.taobao.org/postcss-selector-parser/download/postcss-selector-parser-3.1.1.tgz", "integrity": "sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU=", "requires": { - "dot-prop": "^4.1.1", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" + "dot-prop": "4.2.0", + "indexes-of": "1.0.1", + "uniq": "1.0.1" } } } @@ -4190,7 +4190,7 @@ "resolved": "http://registry.npm.taobao.org/postcss-normalize-charset/download/postcss-normalize-charset-4.0.1.tgz", "integrity": "sha1-izWt067oOhNrBHHg1ZvlilAoXdQ=", "requires": { - "postcss": "^7.0.0" + "postcss": "7.0.7" } }, "postcss-normalize-display-values": { @@ -4198,9 +4198,9 @@ "resolved": "http://registry.npm.taobao.org/postcss-normalize-display-values/download/postcss-normalize-display-values-4.0.1.tgz", "integrity": "sha1-2ag9R8cW6KmA8i9jLIsEWM+0ikw=", "requires": { - "cssnano-util-get-match": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" + "cssnano-util-get-match": "4.0.0", + "postcss": "7.0.7", + "postcss-value-parser": "3.3.1" } }, "postcss-normalize-positions": { @@ -4208,10 +4208,10 @@ "resolved": "http://registry.npm.taobao.org/postcss-normalize-positions/download/postcss-normalize-positions-4.0.1.tgz", "integrity": "sha1-7i1LZ4GMlhlkxr4J0XmJS5T9a6E=", "requires": { - "cssnano-util-get-arguments": "^4.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" + "cssnano-util-get-arguments": "4.0.0", + "has": "1.0.3", + "postcss": "7.0.7", + "postcss-value-parser": "3.3.1" } }, "postcss-normalize-repeat-style": { @@ -4219,10 +4219,10 @@ "resolved": "http://registry.npm.taobao.org/postcss-normalize-repeat-style/download/postcss-normalize-repeat-style-4.0.1.tgz", "integrity": "sha1-UpPyNLlNdmmp+AVJXTW4KlgcUOU=", "requires": { - "cssnano-util-get-arguments": "^4.0.0", - "cssnano-util-get-match": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" + "cssnano-util-get-arguments": "4.0.0", + "cssnano-util-get-match": "4.0.0", + "postcss": "7.0.7", + "postcss-value-parser": "3.3.1" } }, "postcss-normalize-string": { @@ -4230,9 +4230,9 @@ "resolved": "http://registry.npm.taobao.org/postcss-normalize-string/download/postcss-normalize-string-4.0.1.tgz", "integrity": "sha1-I8UDDCzCQXX2bJFPpRmeLjwQ/vM=", "requires": { - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" + "has": "1.0.3", + "postcss": "7.0.7", + "postcss-value-parser": "3.3.1" } }, "postcss-normalize-timing-functions": { @@ -4240,9 +4240,9 @@ "resolved": "http://registry.npm.taobao.org/postcss-normalize-timing-functions/download/postcss-normalize-timing-functions-4.0.1.tgz", "integrity": "sha1-i+g+C5yz/y0avd7gMqSRCPBfldc=", "requires": { - "cssnano-util-get-match": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" + "cssnano-util-get-match": "4.0.0", + "postcss": "7.0.7", + "postcss-value-parser": "3.3.1" } }, "postcss-normalize-unicode": { @@ -4250,9 +4250,9 @@ "resolved": "http://registry.npm.taobao.org/postcss-normalize-unicode/download/postcss-normalize-unicode-4.0.1.tgz", "integrity": "sha1-hBvUj9zzAZrUuqdJOj02O1KuHPs=", "requires": { - "browserslist": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" + "browserslist": "4.3.6", + "postcss": "7.0.7", + "postcss-value-parser": "3.3.1" } }, "postcss-normalize-url": { @@ -4260,10 +4260,10 @@ "resolved": "http://registry.npm.taobao.org/postcss-normalize-url/download/postcss-normalize-url-4.0.1.tgz", "integrity": "sha1-EOQ3+GvHx+WPe5ZS7YeNqqlfquE=", "requires": { - "is-absolute-url": "^2.0.0", - "normalize-url": "^3.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" + "is-absolute-url": "2.1.0", + "normalize-url": "3.3.0", + "postcss": "7.0.7", + "postcss-value-parser": "3.3.1" }, "dependencies": { "normalize-url": { @@ -4278,8 +4278,8 @@ "resolved": "http://registry.npm.taobao.org/postcss-normalize-whitespace/download/postcss-normalize-whitespace-4.0.1.tgz", "integrity": "sha1-0Uy2ObYSOEGKyLyNO3vdZfyGV14=", "requires": { - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" + "postcss": "7.0.7", + "postcss-value-parser": "3.3.1" } }, "postcss-ordered-values": { @@ -4287,9 +4287,9 @@ "resolved": "http://registry.npm.taobao.org/postcss-ordered-values/download/postcss-ordered-values-4.1.1.tgz", "integrity": "sha1-LjtDLvPkibGDM67KHxKV64m+n8I=", "requires": { - "cssnano-util-get-arguments": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" + "cssnano-util-get-arguments": "4.0.0", + "postcss": "7.0.7", + "postcss-value-parser": "3.3.1" } }, "postcss-reduce-initial": { @@ -4297,10 +4297,10 @@ "resolved": "http://registry.npm.taobao.org/postcss-reduce-initial/download/postcss-reduce-initial-4.0.2.tgz", "integrity": "sha1-usjjJdZ1EO4B+kYGdtyOqeO0DxU=", "requires": { - "browserslist": "^4.0.0", - "caniuse-api": "^3.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0" + "browserslist": "4.3.6", + "caniuse-api": "3.0.0", + "has": "1.0.3", + "postcss": "7.0.7" } }, "postcss-reduce-transforms": { @@ -4308,10 +4308,10 @@ "resolved": "http://registry.npm.taobao.org/postcss-reduce-transforms/download/postcss-reduce-transforms-4.0.1.tgz", "integrity": "sha1-hgDVVTvdOtZA9Dv/getS+HYNRWE=", "requires": { - "cssnano-util-get-match": "^4.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" + "cssnano-util-get-match": "4.0.0", + "has": "1.0.3", + "postcss": "7.0.7", + "postcss-value-parser": "3.3.1" } }, "postcss-selector-parser": { @@ -4319,9 +4319,9 @@ "resolved": "http://registry.npm.taobao.org/postcss-selector-parser/download/postcss-selector-parser-5.0.0.tgz", "integrity": "sha1-JJBENWaXsztk8aj3yAki3d7nGVw=", "requires": { - "cssesc": "^2.0.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" + "cssesc": "2.0.0", + "indexes-of": "1.0.1", + "uniq": "1.0.1" } }, "postcss-svgo": { @@ -4329,10 +4329,10 @@ "resolved": "http://registry.npm.taobao.org/postcss-svgo/download/postcss-svgo-4.0.1.tgz", "integrity": "sha1-VijNs48BXea1iM5tC/ByS0krWB0=", "requires": { - "is-svg": "^3.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0", - "svgo": "^1.0.0" + "is-svg": "3.0.0", + "postcss": "7.0.7", + "postcss-value-parser": "3.3.1", + "svgo": "1.1.1" } }, "postcss-unique-selectors": { @@ -4340,9 +4340,9 @@ "resolved": "http://registry.npm.taobao.org/postcss-unique-selectors/download/postcss-unique-selectors-4.0.1.tgz", "integrity": "sha1-lEaRHzKJv9ZMbWgPBzwDsfnuS6w=", "requires": { - "alphanum-sort": "^1.0.0", - "postcss": "^7.0.0", - "uniqs": "^2.0.0" + "alphanum-sort": "1.0.2", + "postcss": "7.0.7", + "uniqs": "2.0.0" } }, "postcss-value-parser": { @@ -4380,8 +4380,8 @@ "resolved": "http://registry.npm.taobao.org/pump/download/pump-2.0.1.tgz", "integrity": "sha1-Ejma3W5M91Jtlzy8i1zi4pCLOQk=", "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" + "end-of-stream": "1.4.1", + "once": "1.4.0" } }, "pumpify": { @@ -4389,9 +4389,9 @@ "resolved": "http://registry.npm.taobao.org/pumpify/download/pumpify-1.5.1.tgz", "integrity": "sha1-NlE74karJ1cLGjdKXOJ4v9dDcM4=", "requires": { - "duplexify": "^3.6.0", - "inherits": "^2.0.3", - "pump": "^2.0.0" + "duplexify": "3.6.1", + "inherits": "2.0.3", + "pump": "2.0.1" } }, "punycode": { @@ -4404,11 +4404,11 @@ "resolved": "http://registry.npm.taobao.org/purify-css/download/purify-css-1.2.5.tgz", "integrity": "sha1-xLnskHNXZfPiR7pqO0nxMvNIJQA=", "requires": { - "clean-css": "^4.0.12", - "glob": "^7.1.1", - "rework": "^1.0.1", - "uglify-js": "^3.0.6", - "yargs": "^8.0.1" + "clean-css": "4.2.1", + "glob": "7.1.3", + "rework": "1.0.1", + "uglify-js": "3.4.9", + "yargs": "8.0.2" }, "dependencies": { "ansi-regex": { @@ -4421,9 +4421,9 @@ "resolved": "http://registry.npm.taobao.org/cliui/download/cliui-3.2.0.tgz", "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wrap-ansi": "^2.0.0" + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wrap-ansi": "2.1.0" }, "dependencies": { "string-width": { @@ -4431,9 +4431,9 @@ "resolved": "http://registry.npm.taobao.org/string-width/download/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" } } } @@ -4443,7 +4443,7 @@ "resolved": "http://registry.npm.taobao.org/is-fullwidth-code-point/download/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "requires": { - "number-is-nan": "^1.0.0" + "number-is-nan": "1.0.1" } }, "load-json-file": { @@ -4451,10 +4451,10 @@ "resolved": "http://registry.npm.taobao.org/load-json-file/download/load-json-file-2.0.0.tgz", "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "strip-bom": "^3.0.0" + "graceful-fs": "4.1.15", + "parse-json": "2.2.0", + "pify": "2.3.0", + "strip-bom": "3.0.0" } }, "path-type": { @@ -4462,7 +4462,7 @@ "resolved": "http://registry.npm.taobao.org/path-type/download/path-type-2.0.0.tgz", "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", "requires": { - "pify": "^2.0.0" + "pify": "2.3.0" } }, "read-pkg": { @@ -4470,9 +4470,9 @@ "resolved": "http://registry.npm.taobao.org/read-pkg/download/read-pkg-2.0.0.tgz", "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", "requires": { - "load-json-file": "^2.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^2.0.0" + "load-json-file": "2.0.0", + "normalize-package-data": "2.4.0", + "path-type": "2.0.0" } }, "read-pkg-up": { @@ -4480,8 +4480,8 @@ "resolved": "http://registry.npm.taobao.org/read-pkg-up/download/read-pkg-up-2.0.0.tgz", "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", "requires": { - "find-up": "^2.0.0", - "read-pkg": "^2.0.0" + "find-up": "2.1.0", + "read-pkg": "2.0.0" } }, "strip-ansi": { @@ -4489,7 +4489,7 @@ "resolved": "http://registry.npm.taobao.org/strip-ansi/download/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "2.1.1" } }, "strip-bom": { @@ -4502,19 +4502,19 @@ "resolved": "http://registry.npm.taobao.org/yargs/download/yargs-8.0.2.tgz", "integrity": "sha1-YpmpBVsc78lp/355wdkY3Osiw2A=", "requires": { - "camelcase": "^4.1.0", - "cliui": "^3.2.0", - "decamelize": "^1.1.1", - "get-caller-file": "^1.0.1", - "os-locale": "^2.0.0", - "read-pkg-up": "^2.0.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^7.0.0" + "camelcase": "4.1.0", + "cliui": "3.2.0", + "decamelize": "1.2.0", + "get-caller-file": "1.0.3", + "os-locale": "2.1.0", + "read-pkg-up": "2.0.0", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "2.1.1", + "which-module": "2.0.0", + "y18n": "3.2.1", + "yargs-parser": "7.0.0" } }, "yargs-parser": { @@ -4522,7 +4522,7 @@ "resolved": "http://registry.npm.taobao.org/yargs-parser/download/yargs-parser-7.0.0.tgz", "integrity": "sha1-jQrELxbqVd69MyyvTEA4s+P139k=", "requires": { - "camelcase": "^4.1.0" + "camelcase": "4.1.0" } } } @@ -4542,9 +4542,9 @@ "resolved": "http://registry.npm.taobao.org/read-pkg/download/read-pkg-1.1.0.tgz", "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", "requires": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" + "load-json-file": "1.1.0", + "normalize-package-data": "2.4.0", + "path-type": "1.1.0" } }, "read-pkg-up": { @@ -4552,8 +4552,8 @@ "resolved": "http://registry.npm.taobao.org/read-pkg-up/download/read-pkg-up-1.0.1.tgz", "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", "requires": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" + "find-up": "1.1.2", + "read-pkg": "1.1.0" }, "dependencies": { "find-up": { @@ -4561,8 +4561,8 @@ "resolved": "http://registry.npm.taobao.org/find-up/download/find-up-1.1.2.tgz", "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" + "path-exists": "2.1.0", + "pinkie-promise": "2.0.1" } }, "path-exists": { @@ -4570,7 +4570,7 @@ "resolved": "http://registry.npm.taobao.org/path-exists/download/path-exists-2.1.0.tgz", "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", "requires": { - "pinkie-promise": "^2.0.0" + "pinkie-promise": "2.0.1" } } } @@ -4580,13 +4580,13 @@ "resolved": "http://registry.npm.taobao.org/readable-stream/download/readable-stream-2.3.6.tgz", "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" }, "dependencies": { "process-nextick-args": { @@ -4601,9 +4601,9 @@ "resolved": "http://registry.npm.taobao.org/readdirp/download/readdirp-2.2.1.tgz", "integrity": "sha1-DodiKjMlqjPokihcr4tOhGUppSU=", "requires": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" + "graceful-fs": "4.1.15", + "micromatch": "3.1.10", + "readable-stream": "2.3.6" } }, "rechoir": { @@ -4611,7 +4611,7 @@ "resolved": "http://registry.npm.taobao.org/rechoir/download/rechoir-0.6.2.tgz", "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", "requires": { - "resolve": "^1.1.6" + "resolve": "1.8.1" } }, "regex-not": { @@ -4619,8 +4619,8 @@ "resolved": "http://registry.npm.taobao.org/regex-not/download/regex-not-1.0.2.tgz", "integrity": "sha1-H07OJ+ALC2XgJHpoEOaoXYOldSw=", "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" + "extend-shallow": "3.0.2", + "safe-regex": "1.1.0" } }, "relateurl": { @@ -4633,8 +4633,8 @@ "resolved": "https://registry.npmjs.org/remark-clang-format/-/remark-clang-format-1.0.1.tgz", "integrity": "sha512-ztyv9OPExH/TDSSaYJPIwsVbL2yGqSASyZtEcXIOg5O7kwAiJH4jSUhZ6OgUCfrGv/R/kzKscP5FP8VBIDwAtQ==", "requires": { - "clang-format": "^1.2.4", - "unist-util-visit": "^1.4.0" + "clang-format": "1.2.4", + "unist-util-visit": "1.4.0" } }, "remark-copywriting-correct": { @@ -4642,9 +4642,9 @@ "resolved": "https://registry.npmjs.org/remark-copywriting-correct/-/remark-copywriting-correct-0.4.0.tgz", "integrity": "sha512-E0iLgS5qCr4Xw1qelQUwwDTGo7I+5OQPgpRTrrtr0eivde3yxQaPAPFvkwYXQaMRqUyLiSo6efHeku8aX6pA7g==", "requires": { - "copywriting-correct": "^1.0.2", - "unist-util-is": "^2.1.1", - "unist-util-visit": "^1.1.3" + "copywriting-correct": "1.0.5", + "unist-util-is": "2.1.2", + "unist-util-visit": "1.4.0" } }, "remark-lint": { @@ -4652,7 +4652,7 @@ "resolved": "https://registry.npmjs.org/remark-lint/-/remark-lint-6.0.2.tgz", "integrity": "sha512-zrIx7InZLLOUYUViT6lSa8T80pDyl3Ywvliog+4hoc7LoiJZRV74ejq+RBZK70bg/p2dU/CV6ycedgypFFePPg==", "requires": { - "remark-message-control": "^4.0.0" + "remark-message-control": "4.1.0" } }, "remark-lint-blockquote-indentation": { @@ -4660,12 +4660,12 @@ "resolved": "https://registry.npmjs.org/remark-lint-blockquote-indentation/-/remark-lint-blockquote-indentation-1.0.2.tgz", "integrity": "sha512-u3ruA+4ZZOpt3YmTCdCOcYiGBMSQ/b/iJvZs/fibF6rwSBmkod48aGGJVoOLMuIuTYYbbXpzigxS+PeJwN0CDQ==", "requires": { - "mdast-util-to-string": "^1.0.2", - "plur": "^3.0.0", - "unified-lint-rule": "^1.0.0", - "unist-util-generated": "^1.1.0", - "unist-util-position": "^3.0.0", - "unist-util-visit": "^1.1.1" + "mdast-util-to-string": "1.0.5", + "plur": "3.0.1", + "unified-lint-rule": "1.0.3", + "unist-util-generated": "1.1.2", + "unist-util-position": "3.0.1", + "unist-util-visit": "1.4.0" } }, "remark-lint-code-block-style": { @@ -4673,10 +4673,10 @@ "resolved": "https://registry.npmjs.org/remark-lint-code-block-style/-/remark-lint-code-block-style-1.0.2.tgz", "integrity": "sha512-fTSCga/lJ710zBaD808NwqzAatVoLQFizvXWpetygKwoAfXCyMYQ9DUdDE5jdDhwOu2JPnKbxY+4t6m4SrKKWA==", "requires": { - "unified-lint-rule": "^1.0.0", - "unist-util-generated": "^1.1.0", - "unist-util-position": "^3.0.0", - "unist-util-visit": "^1.1.1" + "unified-lint-rule": "1.0.3", + "unist-util-generated": "1.1.2", + "unist-util-position": "3.0.1", + "unist-util-visit": "1.4.0" } }, "remark-lint-definition-case": { @@ -4684,10 +4684,10 @@ "resolved": "https://registry.npmjs.org/remark-lint-definition-case/-/remark-lint-definition-case-1.0.2.tgz", "integrity": "sha512-vzL3IufsgYMdoYzgelryjBbNotMSac2VpIQWPbBrOEaMO8YA0OwFmD4ZxZ/IgqExRJs7CYO1v7Vr1/AhIy6RwA==", "requires": { - "unified-lint-rule": "^1.0.0", - "unist-util-generated": "^1.1.0", - "unist-util-position": "^3.0.0", - "unist-util-visit": "^1.1.1" + "unified-lint-rule": "1.0.3", + "unist-util-generated": "1.1.2", + "unist-util-position": "3.0.1", + "unist-util-visit": "1.4.0" } }, "remark-lint-definition-spacing": { @@ -4695,10 +4695,10 @@ "resolved": "https://registry.npmjs.org/remark-lint-definition-spacing/-/remark-lint-definition-spacing-1.0.2.tgz", "integrity": "sha512-Yg1BcI/nydXii1B6kiqKIBsqDW7KlOCBMpJO2jMGmNuEuZe8sv1AWNmaCtiSCdPuDiX0CZRidklrkrZwAthPdw==", "requires": { - "unified-lint-rule": "^1.0.0", - "unist-util-generated": "^1.1.0", - "unist-util-position": "^3.0.0", - "unist-util-visit": "^1.1.1" + "unified-lint-rule": "1.0.3", + "unist-util-generated": "1.1.2", + "unist-util-position": "3.0.1", + "unist-util-visit": "1.4.0" } }, "remark-lint-emphasis-marker": { @@ -4706,10 +4706,10 @@ "resolved": "https://registry.npmjs.org/remark-lint-emphasis-marker/-/remark-lint-emphasis-marker-1.0.2.tgz", "integrity": "sha512-c+uvvnYesMaqy/X0dU62dbI6/rk+4dxMXdnfLC/NKBA8GU+4kljWqluW797S6nBG94QZjKIv8m49zJl38QfImQ==", "requires": { - "unified-lint-rule": "^1.0.0", - "unist-util-generated": "^1.1.0", - "unist-util-position": "^3.0.0", - "unist-util-visit": "^1.1.1" + "unified-lint-rule": "1.0.3", + "unist-util-generated": "1.1.2", + "unist-util-position": "3.0.1", + "unist-util-visit": "1.4.0" } }, "remark-lint-fenced-code-flag": { @@ -4717,10 +4717,10 @@ "resolved": "https://registry.npmjs.org/remark-lint-fenced-code-flag/-/remark-lint-fenced-code-flag-1.0.2.tgz", "integrity": "sha512-6/412zYtz+qKpFJryEPSMurWr6tO5MTVohJF3byFc3+3SSEZLWY3Dg8gbwFlumZ9T4HgmfUm/LT7Idm96zj0nw==", "requires": { - "unified-lint-rule": "^1.0.0", - "unist-util-generated": "^1.1.0", - "unist-util-position": "^3.0.0", - "unist-util-visit": "^1.1.1" + "unified-lint-rule": "1.0.3", + "unist-util-generated": "1.1.2", + "unist-util-position": "3.0.1", + "unist-util-visit": "1.4.0" } }, "remark-lint-fenced-code-marker": { @@ -4728,10 +4728,10 @@ "resolved": "https://registry.npmjs.org/remark-lint-fenced-code-marker/-/remark-lint-fenced-code-marker-1.0.2.tgz", "integrity": "sha512-yAP59Q1JoI1jjOFCn0GoNx4uDji99ROLvdwvmz7+9YR9guDArBcR4i9Wem/wN6apauWPk2DbAZFavHvbZaT8HQ==", "requires": { - "unified-lint-rule": "^1.0.0", - "unist-util-generated": "^1.1.0", - "unist-util-position": "^3.0.0", - "unist-util-visit": "^1.1.1" + "unified-lint-rule": "1.0.3", + "unist-util-generated": "1.1.2", + "unist-util-position": "3.0.1", + "unist-util-visit": "1.4.0" } }, "remark-lint-file-extension": { @@ -4739,7 +4739,7 @@ "resolved": "https://registry.npmjs.org/remark-lint-file-extension/-/remark-lint-file-extension-1.0.2.tgz", "integrity": "sha512-qx0uki74rmALIKE3r5J3neasbXnz6h+l88OngvpwWkELsnJmfk81JdxfEd0tZ++uTj6CN0TZuhMKad9smfNtRw==", "requires": { - "unified-lint-rule": "^1.0.0" + "unified-lint-rule": "1.0.3" } }, "remark-lint-final-definition": { @@ -4747,10 +4747,10 @@ "resolved": "https://registry.npmjs.org/remark-lint-final-definition/-/remark-lint-final-definition-1.0.2.tgz", "integrity": "sha512-F+n8eauYOJGdcSrnD7w2YgQSERx1rAwXTxStaJ2tLmoXlT7eQgpVGHz1U4Y76cg8OANbq8pT0KTNJ85JNqkq4g==", "requires": { - "unified-lint-rule": "^1.0.0", - "unist-util-generated": "^1.1.0", - "unist-util-position": "^3.0.0", - "unist-util-visit": "^1.1.1" + "unified-lint-rule": "1.0.3", + "unist-util-generated": "1.1.2", + "unist-util-position": "3.0.1", + "unist-util-visit": "1.4.0" } }, "remark-lint-final-newline": { @@ -4758,7 +4758,7 @@ "resolved": "https://registry.npmjs.org/remark-lint-final-newline/-/remark-lint-final-newline-1.0.2.tgz", "integrity": "sha512-hW/lbDwVKtME3jIcJWJ16wBtoJdFPWIiu0fEI93yzNTjeB1g3VSWJp66dHbtCLYwquRS5fr8UlGx7JxIu1kiuA==", "requires": { - "unified-lint-rule": "^1.0.0" + "unified-lint-rule": "1.0.3" } }, "remark-lint-hard-break-spaces": { @@ -4766,10 +4766,10 @@ "resolved": "https://registry.npmjs.org/remark-lint-hard-break-spaces/-/remark-lint-hard-break-spaces-1.0.3.tgz", "integrity": "sha512-GiC0uXeFwef6/Pfo+EYBN0WIVlEFffh+9TdeJ4uLt89ZweaRVDPCTJQqkkuXoiXSPnZGD7cGHdkWCfXU1PaU7Q==", "requires": { - "unified-lint-rule": "^1.0.0", - "unist-util-generated": "^1.1.0", - "unist-util-position": "^3.0.0", - "unist-util-visit": "^1.1.1" + "unified-lint-rule": "1.0.3", + "unist-util-generated": "1.1.2", + "unist-util-position": "3.0.1", + "unist-util-visit": "1.4.0" } }, "remark-lint-heading-increment": { @@ -4777,9 +4777,9 @@ "resolved": "https://registry.npmjs.org/remark-lint-heading-increment/-/remark-lint-heading-increment-1.0.2.tgz", "integrity": "sha512-CE3MmARKFk6LK+nBuOUubhr64LnbJfLNx1gA8XgxWJ4s/gf8yZO23KsaWk3ftVmmwk0d8Eqh4qKg8vvvaMyrWQ==", "requires": { - "unified-lint-rule": "^1.0.0", - "unist-util-generated": "^1.1.0", - "unist-util-visit": "^1.1.1" + "unified-lint-rule": "1.0.3", + "unist-util-generated": "1.1.2", + "unist-util-visit": "1.4.0" } }, "remark-lint-heading-style": { @@ -4787,10 +4787,10 @@ "resolved": "https://registry.npmjs.org/remark-lint-heading-style/-/remark-lint-heading-style-1.0.2.tgz", "integrity": "sha512-d0aIbL8PU5LWfZVI8p49vEV5wWIfD/DdUjc+O8j5E0UWUgcRgPGB66xznkOb8AiniXpcaYggRW8hGZsxoYNt1g==", "requires": { - "mdast-util-heading-style": "^1.0.2", - "unified-lint-rule": "^1.0.0", - "unist-util-generated": "^1.1.0", - "unist-util-visit": "^1.1.1" + "mdast-util-heading-style": "1.0.4", + "unified-lint-rule": "1.0.3", + "unist-util-generated": "1.1.2", + "unist-util-visit": "1.4.0" } }, "remark-lint-link-title-style": { @@ -4798,11 +4798,11 @@ "resolved": "https://registry.npmjs.org/remark-lint-link-title-style/-/remark-lint-link-title-style-1.0.2.tgz", "integrity": "sha512-0yoaSeLek5hWAQM8WETpi7/pY8kAVOOC/G1ZZFKmIQ0LPeWIzbIlPKJVV0vCiW97J3Bpv8PL0TMTwhXeP0KH2w==", "requires": { - "unified-lint-rule": "^1.0.0", - "unist-util-generated": "^1.1.0", - "unist-util-position": "^3.0.0", - "unist-util-visit": "^1.1.1", - "vfile-location": "^2.0.1" + "unified-lint-rule": "1.0.3", + "unist-util-generated": "1.1.2", + "unist-util-position": "3.0.1", + "unist-util-visit": "1.4.0", + "vfile-location": "2.0.3" } }, "remark-lint-list-item-content-indent": { @@ -4810,11 +4810,11 @@ "resolved": "https://registry.npmjs.org/remark-lint-list-item-content-indent/-/remark-lint-list-item-content-indent-1.0.2.tgz", "integrity": "sha512-I7VkspA/jeMmIWZ4cGmW/4oWnT6fP8pn5n11MR7azRMKgooj3N2qGF084UqrWHh/dLJcakJUNl3NTXv1XCS1Mw==", "requires": { - "plur": "^3.0.0", - "unified-lint-rule": "^1.0.0", - "unist-util-generated": "^1.1.0", - "unist-util-position": "^3.0.0", - "unist-util-visit": "^1.1.1" + "plur": "3.0.1", + "unified-lint-rule": "1.0.3", + "unist-util-generated": "1.1.2", + "unist-util-position": "3.0.1", + "unist-util-visit": "1.4.0" } }, "remark-lint-list-item-indent": { @@ -4822,11 +4822,11 @@ "resolved": "https://registry.npmjs.org/remark-lint-list-item-indent/-/remark-lint-list-item-indent-1.0.2.tgz", "integrity": "sha512-ogCCrO8nyuM/0k1bo+O7Ww0S08XxHA9sHh5VdhLwffCTCyOPDoxL1zWCIrAgzPBFZkgjXDQHsOxeUBi5I1ZFcA==", "requires": { - "plur": "^3.0.0", - "unified-lint-rule": "^1.0.0", - "unist-util-generated": "^1.1.0", - "unist-util-position": "^3.0.0", - "unist-util-visit": "^1.1.1" + "plur": "3.0.1", + "unified-lint-rule": "1.0.3", + "unist-util-generated": "1.1.2", + "unist-util-position": "3.0.1", + "unist-util-visit": "1.4.0" } }, "remark-lint-list-item-spacing": { @@ -4834,10 +4834,10 @@ "resolved": "https://registry.npmjs.org/remark-lint-list-item-spacing/-/remark-lint-list-item-spacing-1.1.2.tgz", "integrity": "sha512-IhG28ofW85o/2+eVH1ft1zgQmjxqDhNp3+217EQLQviPt/+jVcMsua4W4ZQECPg0E9473yiY9TKbBodp2kOMkg==", "requires": { - "unified-lint-rule": "^1.0.0", - "unist-util-generated": "^1.1.0", - "unist-util-position": "^3.0.0", - "unist-util-visit": "^1.1.1" + "unified-lint-rule": "1.0.3", + "unist-util-generated": "1.1.2", + "unist-util-position": "3.0.1", + "unist-util-visit": "1.4.0" } }, "remark-lint-maximum-heading-length": { @@ -4845,10 +4845,10 @@ "resolved": "https://registry.npmjs.org/remark-lint-maximum-heading-length/-/remark-lint-maximum-heading-length-1.0.2.tgz", "integrity": "sha512-kDdwgRItpVGhxdUC+kbWn5YisCrtF4KggP8z36z26tBmDuPj1ohjQvfMWY0oKL8I0Y6UuXyE0vQx3m4R8Qrj+A==", "requires": { - "mdast-util-to-string": "^1.0.2", - "unified-lint-rule": "^1.0.0", - "unist-util-generated": "^1.1.0", - "unist-util-visit": "^1.1.1" + "mdast-util-to-string": "1.0.5", + "unified-lint-rule": "1.0.3", + "unist-util-generated": "1.1.2", + "unist-util-visit": "1.4.0" } }, "remark-lint-maximum-line-length": { @@ -4856,10 +4856,10 @@ "resolved": "https://registry.npmjs.org/remark-lint-maximum-line-length/-/remark-lint-maximum-line-length-1.1.0.tgz", "integrity": "sha512-L+jI6+DReoxHyAWRIxABjX8hPDgxB8B5Lzp0/nDYjWbjl7I4vTsdEvejpmP1K8LVvZ7Ew0XcVHd1zt+p2O8tDg==", "requires": { - "unified-lint-rule": "^1.0.0", - "unist-util-generated": "^1.1.0", - "unist-util-position": "^3.0.0", - "unist-util-visit": "^1.1.1" + "unified-lint-rule": "1.0.3", + "unist-util-generated": "1.1.2", + "unist-util-position": "3.0.1", + "unist-util-visit": "1.4.0" } }, "remark-lint-no-auto-link-without-protocol": { @@ -4867,11 +4867,11 @@ "resolved": "https://registry.npmjs.org/remark-lint-no-auto-link-without-protocol/-/remark-lint-no-auto-link-without-protocol-1.0.2.tgz", "integrity": "sha512-3GtkSxOyd6we4b8JdtJsNgt8+3UN+hpw1UiMoE9X96ahc1rqsCFm6miorNUnF/gfPQ1liHBvZUed2SIenDmpkg==", "requires": { - "mdast-util-to-string": "^1.0.2", - "unified-lint-rule": "^1.0.0", - "unist-util-generated": "^1.1.0", - "unist-util-position": "^3.0.0", - "unist-util-visit": "^1.1.1" + "mdast-util-to-string": "1.0.5", + "unified-lint-rule": "1.0.3", + "unist-util-generated": "1.1.2", + "unist-util-position": "3.0.1", + "unist-util-visit": "1.4.0" } }, "remark-lint-no-blockquote-without-marker": { @@ -4879,11 +4879,11 @@ "resolved": "https://registry.npmjs.org/remark-lint-no-blockquote-without-marker/-/remark-lint-no-blockquote-without-marker-2.0.2.tgz", "integrity": "sha512-jkfZ4hFiviZttEo7Ac7GZWFgMQ/bdVPfSluLeuf+qwL8sQvR4ClklKJ0Xbkk3cLRjvlGsc8U8uZR8qqH5MSLoA==", "requires": { - "unified-lint-rule": "^1.0.0", - "unist-util-generated": "^1.1.0", - "unist-util-position": "^3.0.0", - "unist-util-visit": "^1.1.1", - "vfile-location": "^2.0.1" + "unified-lint-rule": "1.0.3", + "unist-util-generated": "1.1.2", + "unist-util-position": "3.0.1", + "unist-util-visit": "1.4.0", + "vfile-location": "2.0.3" } }, "remark-lint-no-consecutive-blank-lines": { @@ -4891,11 +4891,11 @@ "resolved": "https://registry.npmjs.org/remark-lint-no-consecutive-blank-lines/-/remark-lint-no-consecutive-blank-lines-1.0.2.tgz", "integrity": "sha512-KbOm6EX5Yl9uzRC93soTB+HlqtCzu9XJWsV9CVcoDKtNnpKfyTwQOy6dmUbQrLp4xBdNk4s9S9CsemRaHEkFGA==", "requires": { - "plur": "^3.0.0", - "unified-lint-rule": "^1.0.0", - "unist-util-generated": "^1.1.0", - "unist-util-position": "^3.0.0", - "unist-util-visit": "^1.1.1" + "plur": "3.0.1", + "unified-lint-rule": "1.0.3", + "unist-util-generated": "1.1.2", + "unist-util-position": "3.0.1", + "unist-util-visit": "1.4.0" } }, "remark-lint-no-duplicate-headings": { @@ -4903,12 +4903,12 @@ "resolved": "https://registry.npmjs.org/remark-lint-no-duplicate-headings/-/remark-lint-no-duplicate-headings-1.0.2.tgz", "integrity": "sha512-RO3/eQxLjUoHirHIVC+bE5Abzl+gWiJcdPr48gGSP34xfwCeaBAaeorOAxY/hOqOQ/EVNTTA/JHCBVSNPZWIeg==", "requires": { - "mdast-util-to-string": "^1.0.2", - "unified-lint-rule": "^1.0.0", - "unist-util-generated": "^1.1.0", - "unist-util-position": "^3.0.0", - "unist-util-stringify-position": "^1.1.2", - "unist-util-visit": "^1.1.1" + "mdast-util-to-string": "1.0.5", + "unified-lint-rule": "1.0.3", + "unist-util-generated": "1.1.2", + "unist-util-position": "3.0.1", + "unist-util-stringify-position": "1.1.2", + "unist-util-visit": "1.4.0" } }, "remark-lint-no-emphasis-as-heading": { @@ -4916,9 +4916,9 @@ "resolved": "https://registry.npmjs.org/remark-lint-no-emphasis-as-heading/-/remark-lint-no-emphasis-as-heading-1.0.2.tgz", "integrity": "sha512-lKlwiRQOFOoPSwjbZf065RaUr6RZmO82zZYjXhVT9xwMkWXIAQyG0GJuLB2/+rlMEtlgoUD3ePch+Pzf+KrSJQ==", "requires": { - "unified-lint-rule": "^1.0.0", - "unist-util-generated": "^1.1.0", - "unist-util-visit": "^1.1.1" + "unified-lint-rule": "1.0.3", + "unist-util-generated": "1.1.2", + "unist-util-visit": "1.4.0" } }, "remark-lint-no-file-name-articles": { @@ -4926,7 +4926,7 @@ "resolved": "https://registry.npmjs.org/remark-lint-no-file-name-articles/-/remark-lint-no-file-name-articles-1.0.2.tgz", "integrity": "sha512-5FuxJ0Hd2AgVSP1javG51qPbMBWxma1LrCKI6JmBsu/GM7ZYOgemMyH5v4I1ejTPGj7P30xmIjMNSnV8IBMq3g==", "requires": { - "unified-lint-rule": "^1.0.0" + "unified-lint-rule": "1.0.3" } }, "remark-lint-no-file-name-consecutive-dashes": { @@ -4934,7 +4934,7 @@ "resolved": "https://registry.npmjs.org/remark-lint-no-file-name-consecutive-dashes/-/remark-lint-no-file-name-consecutive-dashes-1.0.2.tgz", "integrity": "sha512-VvCxG3AfRm6ROFNJ8+tdOOkk61mEKj+PytB8xg5WNQypKWhhJ734mJ3GzXD4XEov7Bdd1GVXJFXlLFtfoAewHw==", "requires": { - "unified-lint-rule": "^1.0.0" + "unified-lint-rule": "1.0.3" } }, "remark-lint-no-file-name-irregular-characters": { @@ -4942,7 +4942,7 @@ "resolved": "https://registry.npmjs.org/remark-lint-no-file-name-irregular-characters/-/remark-lint-no-file-name-irregular-characters-1.0.2.tgz", "integrity": "sha512-8A+DYXsiPBu0q4cvqtYwzRj6SWrKnPh+oI1H1t64pCQiSnLmG9e3mAUXMxH9PiM6y5OW7Vw8Xh4KYsnRwGEuMQ==", "requires": { - "unified-lint-rule": "^1.0.0" + "unified-lint-rule": "1.0.3" } }, "remark-lint-no-file-name-mixed-case": { @@ -4950,7 +4950,7 @@ "resolved": "https://registry.npmjs.org/remark-lint-no-file-name-mixed-case/-/remark-lint-no-file-name-mixed-case-1.0.2.tgz", "integrity": "sha512-OMH2kpjvDAsyyw8ar9h6WI1kUXSpQ2r2c5JZv3NBNYxwzTBfhCR2MSQq+eEI7yUmD2ehqNUY5LwZTQZG6cK4vw==", "requires": { - "unified-lint-rule": "^1.0.0" + "unified-lint-rule": "1.0.3" } }, "remark-lint-no-file-name-outer-dashes": { @@ -4958,7 +4958,7 @@ "resolved": "https://registry.npmjs.org/remark-lint-no-file-name-outer-dashes/-/remark-lint-no-file-name-outer-dashes-1.0.3.tgz", "integrity": "sha512-imUWm8Bi9PxV+IQtQC2/BV1Yj0VboC9hPMZh3sae8pZvCjXquTyYiSFa7hQxX6KWCNUiRPHMSlaSVvfvM2e4pQ==", "requires": { - "unified-lint-rule": "^1.0.0" + "unified-lint-rule": "1.0.3" } }, "remark-lint-no-heading-punctuation": { @@ -4966,10 +4966,10 @@ "resolved": "https://registry.npmjs.org/remark-lint-no-heading-punctuation/-/remark-lint-no-heading-punctuation-1.0.2.tgz", "integrity": "sha512-nYc2a0ihQ5cPy7elaM0lRPYKEMpEK6EjyJH6pHYlgG8NQwjKXhsVaek0fmAm12PaYoYOGW1pDxfzxnFUocU20g==", "requires": { - "mdast-util-to-string": "^1.0.2", - "unified-lint-rule": "^1.0.0", - "unist-util-generated": "^1.1.0", - "unist-util-visit": "^1.1.1" + "mdast-util-to-string": "1.0.5", + "unified-lint-rule": "1.0.3", + "unist-util-generated": "1.1.2", + "unist-util-visit": "1.4.0" } }, "remark-lint-no-inline-padding": { @@ -4977,10 +4977,10 @@ "resolved": "https://registry.npmjs.org/remark-lint-no-inline-padding/-/remark-lint-no-inline-padding-1.0.2.tgz", "integrity": "sha512-SHYqEH27yxzgcXSyaIzvqImvktDhXGltRSOEhAHiL2nJktuPt3nosFfGy4/oKAJMWJ2N3aMudXq/zuw1dAkQSg==", "requires": { - "mdast-util-to-string": "^1.0.2", - "unified-lint-rule": "^1.0.0", - "unist-util-generated": "^1.1.0", - "unist-util-visit": "^1.1.1" + "mdast-util-to-string": "1.0.5", + "unified-lint-rule": "1.0.3", + "unist-util-generated": "1.1.2", + "unist-util-visit": "1.4.0" } }, "remark-lint-no-literal-urls": { @@ -4988,11 +4988,11 @@ "resolved": "https://registry.npmjs.org/remark-lint-no-literal-urls/-/remark-lint-no-literal-urls-1.0.2.tgz", "integrity": "sha512-+mWZIJA4yAqpKIclcFP5wRy/6hxcPnfU9Xmgp4fR7OD4JQ4JHkKq9O7MUbda14PLez1aMX+Is0O0hWI7OuqsSw==", "requires": { - "mdast-util-to-string": "^1.0.2", - "unified-lint-rule": "^1.0.0", - "unist-util-generated": "^1.1.0", - "unist-util-position": "^3.0.0", - "unist-util-visit": "^1.1.1" + "mdast-util-to-string": "1.0.5", + "unified-lint-rule": "1.0.3", + "unist-util-generated": "1.1.2", + "unist-util-position": "3.0.1", + "unist-util-visit": "1.4.0" } }, "remark-lint-no-multiple-toplevel-headings": { @@ -5000,11 +5000,11 @@ "resolved": "https://registry.npmjs.org/remark-lint-no-multiple-toplevel-headings/-/remark-lint-no-multiple-toplevel-headings-1.0.2.tgz", "integrity": "sha512-Zxkw7wIyMOyYQb5C5NTswSttZPCLqm/60Wnt0TEWzXVDkVk5DrxrCCxbMKgpXve1Co5CXPmMixNr/xYBqzxzWg==", "requires": { - "unified-lint-rule": "^1.0.0", - "unist-util-generated": "^1.1.0", - "unist-util-position": "^3.0.0", - "unist-util-stringify-position": "^1.1.2", - "unist-util-visit": "^1.1.1" + "unified-lint-rule": "1.0.3", + "unist-util-generated": "1.1.2", + "unist-util-position": "3.0.1", + "unist-util-stringify-position": "1.1.2", + "unist-util-visit": "1.4.0" } }, "remark-lint-no-shell-dollars": { @@ -5012,9 +5012,9 @@ "resolved": "https://registry.npmjs.org/remark-lint-no-shell-dollars/-/remark-lint-no-shell-dollars-1.0.2.tgz", "integrity": "sha512-eIjBebX9iOFWbMdjol5JJBXI7ku+7UyJpNrd++rl8QenLLZ76beh+xONCzJw/k5dhEw5voBmQLh7VK9HPU/ang==", "requires": { - "unified-lint-rule": "^1.0.0", - "unist-util-generated": "^1.1.0", - "unist-util-visit": "^1.1.1" + "unified-lint-rule": "1.0.3", + "unist-util-generated": "1.1.2", + "unist-util-visit": "1.4.0" } }, "remark-lint-no-shortcut-reference-image": { @@ -5022,9 +5022,9 @@ "resolved": "https://registry.npmjs.org/remark-lint-no-shortcut-reference-image/-/remark-lint-no-shortcut-reference-image-1.0.2.tgz", "integrity": "sha512-IVYv5pgyf70jYcrn+BNHVO37BuQJg26rFOLzi2mj+/8EdFpolJiJcTvkChJgz5yip7317DmQQSNLX6gCExuDrQ==", "requires": { - "unified-lint-rule": "^1.0.0", - "unist-util-generated": "^1.1.0", - "unist-util-visit": "^1.1.1" + "unified-lint-rule": "1.0.3", + "unist-util-generated": "1.1.2", + "unist-util-visit": "1.4.0" } }, "remark-lint-no-shortcut-reference-link": { @@ -5032,9 +5032,9 @@ "resolved": "https://registry.npmjs.org/remark-lint-no-shortcut-reference-link/-/remark-lint-no-shortcut-reference-link-1.0.3.tgz", "integrity": "sha512-v5mk4wYQL+YRmlOTqi8avpzhoGZg+P42dDRda2jedysDIx7TJBEXUH6oMFEbo/qV6PMmtr7fr066M3RrOrLpiQ==", "requires": { - "unified-lint-rule": "^1.0.0", - "unist-util-generated": "^1.1.0", - "unist-util-visit": "^1.1.1" + "unified-lint-rule": "1.0.3", + "unist-util-generated": "1.1.2", + "unist-util-visit": "1.4.0" } }, "remark-lint-no-table-indentation": { @@ -5042,10 +5042,10 @@ "resolved": "https://registry.npmjs.org/remark-lint-no-table-indentation/-/remark-lint-no-table-indentation-1.0.2.tgz", "integrity": "sha512-wH0lMGV3DGf7WeDLYGle7SODkXNKqmFtGuh6sG5oa0XgA17rI/L35Vq5tal4DE/5gQG+l4+/0Iy9FPKdBODSDA==", "requires": { - "unified-lint-rule": "^1.0.0", - "unist-util-generated": "^1.1.0", - "unist-util-position": "^3.0.0", - "unist-util-visit": "^1.1.1" + "unified-lint-rule": "1.0.3", + "unist-util-generated": "1.1.2", + "unist-util-position": "3.0.1", + "unist-util-visit": "1.4.0" } }, "remark-lint-no-tabs": { @@ -5053,8 +5053,8 @@ "resolved": "https://registry.npmjs.org/remark-lint-no-tabs/-/remark-lint-no-tabs-1.0.2.tgz", "integrity": "sha512-jPjRLHyzO4lO6orhOmHd6AN6mVc/uMWvYZ3qD41dniktnLyHEbIG6DpPxixjfpmEe0wi73RXMywKHrWshLJwAg==", "requires": { - "unified-lint-rule": "^1.0.0", - "vfile-location": "^2.0.1" + "unified-lint-rule": "1.0.3", + "vfile-location": "2.0.3" } }, "remark-lint-ordered-list-marker-style": { @@ -5062,10 +5062,10 @@ "resolved": "https://registry.npmjs.org/remark-lint-ordered-list-marker-style/-/remark-lint-ordered-list-marker-style-1.0.2.tgz", "integrity": "sha512-4EHuHxZqy8IT4k+4Vc8P38I34AiZfgl07fS5/iqGhCdoSMCvvxdOuzTWTgpDFbx/W2QpHelBfJ+FtOp+E0J4Lg==", "requires": { - "unified-lint-rule": "^1.0.0", - "unist-util-generated": "^1.1.0", - "unist-util-position": "^3.0.0", - "unist-util-visit": "^1.1.1" + "unified-lint-rule": "1.0.3", + "unist-util-generated": "1.1.2", + "unist-util-position": "3.0.1", + "unist-util-visit": "1.4.0" } }, "remark-lint-ordered-list-marker-value": { @@ -5073,10 +5073,10 @@ "resolved": "https://registry.npmjs.org/remark-lint-ordered-list-marker-value/-/remark-lint-ordered-list-marker-value-1.0.2.tgz", "integrity": "sha512-vIPD07u+FBjTjEETZ+UWUp2nydzvOe5AHIX812JlNXWuHYuCybq8DGnkYUcoiK3HbIE+KdG+e7C5xHkim0PSjw==", "requires": { - "unified-lint-rule": "^1.0.0", - "unist-util-generated": "^1.1.0", - "unist-util-position": "^3.0.0", - "unist-util-visit": "^1.1.1" + "unified-lint-rule": "1.0.3", + "unist-util-generated": "1.1.2", + "unist-util-position": "3.0.1", + "unist-util-visit": "1.4.0" } }, "remark-lint-rule-style": { @@ -5084,10 +5084,10 @@ "resolved": "https://registry.npmjs.org/remark-lint-rule-style/-/remark-lint-rule-style-1.0.2.tgz", "integrity": "sha512-D9mMPKA7rtCe4Yx+ryip6FyfNG9uGOaHxRgJClfte7D66QzxiiWtHYyNCXI4rkv8Ax9PrEdpWCPcIl3D2LrXhw==", "requires": { - "unified-lint-rule": "^1.0.0", - "unist-util-generated": "^1.1.0", - "unist-util-position": "^3.0.0", - "unist-util-visit": "^1.1.1" + "unified-lint-rule": "1.0.3", + "unist-util-generated": "1.1.2", + "unist-util-position": "3.0.1", + "unist-util-visit": "1.4.0" } }, "remark-lint-strong-marker": { @@ -5095,10 +5095,10 @@ "resolved": "https://registry.npmjs.org/remark-lint-strong-marker/-/remark-lint-strong-marker-1.0.2.tgz", "integrity": "sha512-oUSKqYJVLgbXe25NmcTOfQ8wsFasc+qhEoGjPEGPuJMV2aZIGuOEbGVqD5B1ckYGBEwbTuet3btvMohz8HaBDQ==", "requires": { - "unified-lint-rule": "^1.0.0", - "unist-util-generated": "^1.1.0", - "unist-util-position": "^3.0.0", - "unist-util-visit": "^1.1.1" + "unified-lint-rule": "1.0.3", + "unist-util-generated": "1.1.2", + "unist-util-position": "3.0.1", + "unist-util-visit": "1.4.0" } }, "remark-lint-table-cell-padding": { @@ -5106,10 +5106,10 @@ "resolved": "https://registry.npmjs.org/remark-lint-table-cell-padding/-/remark-lint-table-cell-padding-1.0.2.tgz", "integrity": "sha512-uYm8ia0joAFeK0XLpxVtGW37Ry1XRBDmWH1gDiO2MXWcUip1w1Brvyue4H8JfXB4IM+S5eI/zPR5zN5Wpj9kfA==", "requires": { - "unified-lint-rule": "^1.0.0", - "unist-util-generated": "^1.1.0", - "unist-util-position": "^3.0.0", - "unist-util-visit": "^1.1.1" + "unified-lint-rule": "1.0.3", + "unist-util-generated": "1.1.2", + "unist-util-position": "3.0.1", + "unist-util-visit": "1.4.0" } }, "remark-lint-table-pipe-alignment": { @@ -5117,10 +5117,10 @@ "resolved": "https://registry.npmjs.org/remark-lint-table-pipe-alignment/-/remark-lint-table-pipe-alignment-1.0.2.tgz", "integrity": "sha512-gLJwduvBI2soR7Dyf39KGUl3M9ZCK/7pFfWBeOv8J27D7px/1lXooqlX4Y9NQ/+9jc7DyLF9upPxh7UWm7UXGg==", "requires": { - "unified-lint-rule": "^1.0.0", - "unist-util-generated": "^1.1.0", - "unist-util-position": "^3.0.0", - "unist-util-visit": "^1.1.1" + "unified-lint-rule": "1.0.3", + "unist-util-generated": "1.1.2", + "unist-util-position": "3.0.1", + "unist-util-visit": "1.4.0" } }, "remark-lint-table-pipes": { @@ -5128,10 +5128,10 @@ "resolved": "https://registry.npmjs.org/remark-lint-table-pipes/-/remark-lint-table-pipes-1.0.2.tgz", "integrity": "sha512-BGKcOviuUC6fILIOPYFe6awqk57ApzNJpK3OYBrweGoFF55nZ/qf3q6JpzA0chd6wKj7VrcfQEd3QSQQ+8Wcrw==", "requires": { - "unified-lint-rule": "^1.0.0", - "unist-util-generated": "^1.1.0", - "unist-util-position": "^3.0.0", - "unist-util-visit": "^1.1.1" + "unified-lint-rule": "1.0.3", + "unist-util-generated": "1.1.2", + "unist-util-position": "3.0.1", + "unist-util-visit": "1.4.0" } }, "remark-lint-unordered-list-marker-style": { @@ -5139,10 +5139,10 @@ "resolved": "https://registry.npmjs.org/remark-lint-unordered-list-marker-style/-/remark-lint-unordered-list-marker-style-1.0.2.tgz", "integrity": "sha512-qdnF9JuMWzFJzGIfdAWfOHyjad8dqIQSs+cTzqMlNZHOGrrCJdTUWzybzcZMGn1yuwreklZdHKhOglXQFwSD3A==", "requires": { - "unified-lint-rule": "^1.0.0", - "unist-util-generated": "^1.1.0", - "unist-util-position": "^3.0.0", - "unist-util-visit": "^1.1.1" + "unified-lint-rule": "1.0.3", + "unist-util-generated": "1.1.2", + "unist-util-position": "3.0.1", + "unist-util-visit": "1.4.0" } }, "remark-math": { @@ -5150,7 +5150,7 @@ "resolved": "http://registry.npm.taobao.org/remark-math/download/remark-math-1.0.5.tgz", "integrity": "sha1-EQpuf3zTXSR5Vkbe+hjS2Y8lE5s=", "requires": { - "trim-trailing-lines": "^1.1.0" + "trim-trailing-lines": "1.1.1" } }, "remark-math-space": { @@ -5163,9 +5163,9 @@ "resolved": "https://registry.npmjs.org/remark-message-control/-/remark-message-control-4.1.0.tgz", "integrity": "sha512-e1dszks4YKY7hLAkhS2367jBjBpAfvi+kVgSN/tOFrdp3qxITjiNR5fOFnyYF8vvorkQ9uxlKJoZUOW8T7rKDg==", "requires": { - "mdast-comment-marker": "^1.0.0", - "unified-message-control": "^1.0.0", - "xtend": "^4.0.1" + "mdast-comment-marker": "1.0.3", + "unified-message-control": "1.0.4", + "xtend": "4.0.1" } }, "remark-pangu": { @@ -5173,9 +5173,9 @@ "resolved": "https://registry.npmjs.org/remark-pangu/-/remark-pangu-1.0.1.tgz", "integrity": "sha1-t4QksIP3AlR2Ks6lMyIKYmZNAcM=", "requires": { - "pangu": "^3.3.0", - "unist-util-is": "^2.1.1", - "unist-util-visit": "^1.1.3" + "pangu": "3.3.0", + "unist-util-is": "2.1.2", + "unist-util-visit": "1.4.0" } }, "remark-preset-lint-markdown-style-guide": { @@ -5183,51 +5183,51 @@ "resolved": "https://registry.npmjs.org/remark-preset-lint-markdown-style-guide/-/remark-preset-lint-markdown-style-guide-2.1.2.tgz", "integrity": "sha512-0mYeeO084o3ZuDSQCvj5vpMZEGQ/HxPcO5vdNpicu+wKpuGNKV2hYZeHbXa/uZV0gjiQeuRmCHIDgNaDsMj5fg==", "requires": { - "remark-lint": "^6.0.0", - "remark-lint-blockquote-indentation": "^1.0.0", - "remark-lint-code-block-style": "^1.0.0", - "remark-lint-definition-case": "^1.0.0", - "remark-lint-definition-spacing": "^1.0.0", - "remark-lint-emphasis-marker": "^1.0.0", - "remark-lint-fenced-code-flag": "^1.0.0", - "remark-lint-fenced-code-marker": "^1.0.0", - "remark-lint-file-extension": "^1.0.0", - "remark-lint-final-definition": "^1.0.0", - "remark-lint-hard-break-spaces": "^1.0.0", - "remark-lint-heading-increment": "^1.0.0", - "remark-lint-heading-style": "^1.0.0", - "remark-lint-link-title-style": "^1.0.0", - "remark-lint-list-item-content-indent": "^1.0.0", - "remark-lint-list-item-indent": "^1.0.0", - "remark-lint-list-item-spacing": "^1.0.0", - "remark-lint-maximum-heading-length": "^1.0.0", - "remark-lint-maximum-line-length": "^1.0.0", - "remark-lint-no-auto-link-without-protocol": "^1.0.0", - "remark-lint-no-blockquote-without-marker": "^2.0.0", - "remark-lint-no-consecutive-blank-lines": "^1.0.0", - "remark-lint-no-duplicate-headings": "^1.0.0", - "remark-lint-no-emphasis-as-heading": "^1.0.0", - "remark-lint-no-file-name-articles": "^1.0.0", - "remark-lint-no-file-name-consecutive-dashes": "^1.0.0", - "remark-lint-no-file-name-irregular-characters": "^1.0.0", - "remark-lint-no-file-name-mixed-case": "^1.0.0", - "remark-lint-no-file-name-outer-dashes": "^1.0.0", - "remark-lint-no-heading-punctuation": "^1.0.0", - "remark-lint-no-inline-padding": "^1.0.0", - "remark-lint-no-literal-urls": "^1.0.0", - "remark-lint-no-multiple-toplevel-headings": "^1.0.0", - "remark-lint-no-shell-dollars": "^1.0.0", - "remark-lint-no-shortcut-reference-image": "^1.0.0", - "remark-lint-no-shortcut-reference-link": "^1.0.0", - "remark-lint-no-table-indentation": "^1.0.0", - "remark-lint-ordered-list-marker-style": "^1.0.0", - "remark-lint-ordered-list-marker-value": "^1.0.0", - "remark-lint-rule-style": "^1.0.0", - "remark-lint-strong-marker": "^1.0.0", - "remark-lint-table-cell-padding": "^1.0.0", - "remark-lint-table-pipe-alignment": "^1.0.0", - "remark-lint-table-pipes": "^1.0.0", - "remark-lint-unordered-list-marker-style": "^1.0.0" + "remark-lint": "6.0.2", + "remark-lint-blockquote-indentation": "1.0.2", + "remark-lint-code-block-style": "1.0.2", + "remark-lint-definition-case": "1.0.2", + "remark-lint-definition-spacing": "1.0.2", + "remark-lint-emphasis-marker": "1.0.2", + "remark-lint-fenced-code-flag": "1.0.2", + "remark-lint-fenced-code-marker": "1.0.2", + "remark-lint-file-extension": "1.0.2", + "remark-lint-final-definition": "1.0.2", + "remark-lint-hard-break-spaces": "1.0.3", + "remark-lint-heading-increment": "1.0.2", + "remark-lint-heading-style": "1.0.2", + "remark-lint-link-title-style": "1.0.2", + "remark-lint-list-item-content-indent": "1.0.2", + "remark-lint-list-item-indent": "1.0.2", + "remark-lint-list-item-spacing": "1.1.2", + "remark-lint-maximum-heading-length": "1.0.2", + "remark-lint-maximum-line-length": "1.1.0", + "remark-lint-no-auto-link-without-protocol": "1.0.2", + "remark-lint-no-blockquote-without-marker": "2.0.2", + "remark-lint-no-consecutive-blank-lines": "1.0.2", + "remark-lint-no-duplicate-headings": "1.0.2", + "remark-lint-no-emphasis-as-heading": "1.0.2", + "remark-lint-no-file-name-articles": "1.0.2", + "remark-lint-no-file-name-consecutive-dashes": "1.0.2", + "remark-lint-no-file-name-irregular-characters": "1.0.2", + "remark-lint-no-file-name-mixed-case": "1.0.2", + "remark-lint-no-file-name-outer-dashes": "1.0.3", + "remark-lint-no-heading-punctuation": "1.0.2", + "remark-lint-no-inline-padding": "1.0.2", + "remark-lint-no-literal-urls": "1.0.2", + "remark-lint-no-multiple-toplevel-headings": "1.0.2", + "remark-lint-no-shell-dollars": "1.0.2", + "remark-lint-no-shortcut-reference-image": "1.0.2", + "remark-lint-no-shortcut-reference-link": "1.0.3", + "remark-lint-no-table-indentation": "1.0.2", + "remark-lint-ordered-list-marker-style": "1.0.2", + "remark-lint-ordered-list-marker-value": "1.0.2", + "remark-lint-rule-style": "1.0.2", + "remark-lint-strong-marker": "1.0.2", + "remark-lint-table-cell-padding": "1.0.2", + "remark-lint-table-pipe-alignment": "1.0.2", + "remark-lint-table-pipes": "1.0.2", + "remark-lint-unordered-list-marker-style": "1.0.2" } }, "remove-bom-buffer": { @@ -5235,8 +5235,8 @@ "resolved": "http://registry.npm.taobao.org/remove-bom-buffer/download/remove-bom-buffer-3.0.0.tgz", "integrity": "sha1-wr8eN3Ug0yT2I4kuM8EMrCwlK1M=", "requires": { - "is-buffer": "^1.1.5", - "is-utf8": "^0.2.1" + "is-buffer": "1.1.6", + "is-utf8": "0.2.1" } }, "remove-bom-stream": { @@ -5244,9 +5244,9 @@ "resolved": "http://registry.npm.taobao.org/remove-bom-stream/download/remove-bom-stream-1.2.0.tgz", "integrity": "sha1-BfGlk/FuQuH7kOv1nejlaVJflSM=", "requires": { - "remove-bom-buffer": "^3.0.0", - "safe-buffer": "^5.1.0", - "through2": "^2.0.3" + "remove-bom-buffer": "3.0.0", + "safe-buffer": "5.1.2", + "through2": "2.0.5" } }, "remove-trailing-separator": { @@ -5274,9 +5274,9 @@ "resolved": "http://registry.npm.taobao.org/replace-homedir/download/replace-homedir-1.0.0.tgz", "integrity": "sha1-6H9tUTuSjd6AgmDBK+f+xv9ueYw=", "requires": { - "homedir-polyfill": "^1.0.1", - "is-absolute": "^1.0.0", - "remove-trailing-separator": "^1.1.0" + "homedir-polyfill": "1.0.1", + "is-absolute": "1.0.0", + "remove-trailing-separator": "1.1.0" } }, "request": { @@ -5284,26 +5284,26 @@ "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.0", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" + "aws-sign2": "0.7.0", + "aws4": "1.8.0", + "caseless": "0.12.0", + "combined-stream": "1.0.6", + "extend": "3.0.2", + "forever-agent": "0.6.1", + "form-data": "2.3.2", + "har-validator": "5.1.0", + "http-signature": "1.2.0", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.20", + "oauth-sign": "0.9.0", + "performance-now": "2.1.0", + "qs": "6.5.2", + "safe-buffer": "5.1.2", + "tough-cookie": "2.4.3", + "tunnel-agent": "0.6.0", + "uuid": "3.3.2" } }, "request-promise-core": { @@ -5311,7 +5311,7 @@ "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz", "integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=", "requires": { - "lodash": "^4.13.1" + "lodash": "4.17.10" } }, "request-promise-native": { @@ -5320,8 +5320,8 @@ "integrity": "sha1-UoF3D2jgyXGeUWP9P6tIIhX0/aU=", "requires": { "request-promise-core": "1.1.1", - "stealthy-require": "^1.1.0", - "tough-cookie": ">=2.3.3" + "stealthy-require": "1.1.1", + "tough-cookie": "2.4.3" } }, "require-directory": { @@ -5344,7 +5344,7 @@ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", "requires": { - "path-parse": "^1.0.5" + "path-parse": "1.0.6" } }, "resolve-dir": { @@ -5352,8 +5352,8 @@ "resolved": "http://registry.npm.taobao.org/resolve-dir/download/resolve-dir-1.0.1.tgz", "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", "requires": { - "expand-tilde": "^2.0.0", - "global-modules": "^1.0.0" + "expand-tilde": "2.0.2", + "global-modules": "1.0.0" } }, "resolve-from": { @@ -5366,7 +5366,7 @@ "resolved": "http://registry.npm.taobao.org/resolve-options/download/resolve-options-1.1.0.tgz", "integrity": "sha1-MrueOcBtZzONyTeMDW1gdFZq0TE=", "requires": { - "value-or-function": "^3.0.0" + "value-or-function": "3.0.0" } }, "resolve-url": { @@ -5384,8 +5384,8 @@ "resolved": "http://registry.npm.taobao.org/rework/download/rework-1.0.1.tgz", "integrity": "sha1-MIBqhBNCtUUQqkEQhQzUhTQUSqc=", "requires": { - "convert-source-map": "^0.3.3", - "css": "^2.0.0" + "convert-source-map": "0.3.5", + "css": "2.2.4" }, "dependencies": { "convert-source-map": { @@ -5415,7 +5415,7 @@ "resolved": "http://registry.npm.taobao.org/safe-regex/download/safe-regex-1.1.0.tgz", "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "requires": { - "ret": "~0.1.10" + "ret": "0.1.15" } }, "safer-buffer": { @@ -5438,7 +5438,7 @@ "resolved": "http://registry.npm.taobao.org/semver-greatest-satisfied-range/download/semver-greatest-satisfied-range-1.1.0.tgz", "integrity": "sha1-E+jCZYq5aRywzXEJMkAoDTb3els=", "requires": { - "sver-compat": "^1.5.0" + "sver-compat": "1.5.0" } }, "set-blocking": { @@ -5451,10 +5451,10 @@ "resolved": "http://registry.npm.taobao.org/set-value/download/set-value-2.0.0.tgz", "integrity": "sha1-ca5KiPD+77v1LR6mBPP7MV67YnQ=", "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" + "extend-shallow": "2.0.1", + "is-extendable": "0.1.1", + "is-plain-object": "2.0.4", + "split-string": "3.1.0" }, "dependencies": { "extend-shallow": { @@ -5462,7 +5462,7 @@ "resolved": "http://registry.npm.taobao.org/extend-shallow/download/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -5472,7 +5472,7 @@ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", "requires": { - "shebang-regex": "^1.0.0" + "shebang-regex": "1.0.0" } }, "shebang-regex": { @@ -5490,7 +5490,7 @@ "resolved": "http://registry.npm.taobao.org/simple-swizzle/download/simple-swizzle-0.2.2.tgz", "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", "requires": { - "is-arrayish": "^0.3.1" + "is-arrayish": "0.3.2" }, "dependencies": { "is-arrayish": { @@ -5510,14 +5510,14 @@ "resolved": "http://registry.npm.taobao.org/snapdragon/download/snapdragon-0.8.2.tgz", "integrity": "sha1-ZJIufFZbDhQgS6GqfWlkJ40lGC0=", "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" + "base": "0.11.2", + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "map-cache": "0.2.2", + "source-map": "0.5.7", + "source-map-resolve": "0.5.2", + "use": "3.1.1" }, "dependencies": { "define-property": { @@ -5525,7 +5525,7 @@ "resolved": "http://registry.npm.taobao.org/define-property/download/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "requires": { - "is-descriptor": "^0.1.0" + "is-descriptor": "0.1.6" } }, "extend-shallow": { @@ -5533,7 +5533,7 @@ "resolved": "http://registry.npm.taobao.org/extend-shallow/download/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } }, "source-map": { @@ -5548,9 +5548,9 @@ "resolved": "http://registry.npm.taobao.org/snapdragon-node/download/snapdragon-node-2.1.1.tgz", "integrity": "sha1-bBdfhv8UvbByRWPo88GwIaKGhTs=", "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" + "define-property": "1.0.0", + "isobject": "3.0.1", + "snapdragon-util": "3.0.1" }, "dependencies": { "define-property": { @@ -5558,7 +5558,7 @@ "resolved": "http://registry.npm.taobao.org/define-property/download/define-property-1.0.0.tgz", "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "requires": { - "is-descriptor": "^1.0.0" + "is-descriptor": "1.0.2" } }, "is-accessor-descriptor": { @@ -5566,7 +5566,7 @@ "resolved": "http://registry.npm.taobao.org/is-accessor-descriptor/download/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.2" } }, "is-data-descriptor": { @@ -5574,7 +5574,7 @@ "resolved": "http://registry.npm.taobao.org/is-data-descriptor/download/is-data-descriptor-1.0.0.tgz", "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.2" } }, "is-descriptor": { @@ -5582,9 +5582,9 @@ "resolved": "http://registry.npm.taobao.org/is-descriptor/download/is-descriptor-1.0.2.tgz", "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" } } } @@ -5594,7 +5594,7 @@ "resolved": "http://registry.npm.taobao.org/snapdragon-util/download/snapdragon-util-3.0.1.tgz", "integrity": "sha1-+VZHlIbyrNeXAGk/b3uAXkWrVuI=", "requires": { - "kind-of": "^3.2.0" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -5602,7 +5602,7 @@ "resolved": "http://registry.npm.taobao.org/kind-of/download/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } @@ -5617,11 +5617,11 @@ "resolved": "http://registry.npm.taobao.org/source-map-resolve/download/source-map-resolve-0.5.2.tgz", "integrity": "sha1-cuLMNAlVQ+Q7LGKyxMENSpBU8lk=", "requires": { - "atob": "^2.1.1", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" + "atob": "2.1.2", + "decode-uri-component": "0.2.0", + "resolve-url": "0.2.1", + "source-map-url": "0.4.0", + "urix": "0.1.0" } }, "source-map-url": { @@ -5639,8 +5639,8 @@ "resolved": "http://registry.npm.taobao.org/spdx-correct/download/spdx-correct-3.1.0.tgz", "integrity": "sha1-+4PlBERSaPFUsHTiGMh8ADzTHfQ=", "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" + "spdx-expression-parse": "3.0.0", + "spdx-license-ids": "3.0.3" } }, "spdx-exceptions": { @@ -5653,8 +5653,8 @@ "resolved": "http://registry.npm.taobao.org/spdx-expression-parse/download/spdx-expression-parse-3.0.0.tgz", "integrity": "sha1-meEZt6XaAOBUkcn6M4t5BII7QdA=", "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" + "spdx-exceptions": "2.2.0", + "spdx-license-ids": "3.0.3" } }, "spdx-license-ids": { @@ -5667,7 +5667,7 @@ "resolved": "http://registry.npm.taobao.org/split-string/download/split-string-3.1.0.tgz", "integrity": "sha1-fLCd2jqGWFcFxks5pkZgOGguj+I=", "requires": { - "extend-shallow": "^3.0.0" + "extend-shallow": "3.0.2" } }, "sprintf-js": { @@ -5680,15 +5680,15 @@ "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" + "asn1": "0.2.4", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.2", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.2", + "getpass": "0.1.7", + "jsbn": "0.1.1", + "safer-buffer": "2.1.2", + "tweetnacl": "0.14.5" } }, "stable": { @@ -5706,8 +5706,8 @@ "resolved": "http://registry.npm.taobao.org/static-extend/download/static-extend-0.1.2.tgz", "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" + "define-property": "0.2.5", + "object-copy": "0.1.0" }, "dependencies": { "define-property": { @@ -5715,7 +5715,7 @@ "resolved": "http://registry.npm.taobao.org/define-property/download/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "requires": { - "is-descriptor": "^0.1.0" + "is-descriptor": "0.1.6" } } } @@ -5740,8 +5740,8 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" } }, "string_decoder": { @@ -5749,7 +5749,7 @@ "resolved": "http://registry.npm.taobao.org/string_decoder/download/string_decoder-1.1.1.tgz", "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.2" } }, "strip-ansi": { @@ -5757,7 +5757,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "3.0.0" } }, "strip-bom": { @@ -5765,7 +5765,7 @@ "resolved": "http://registry.npm.taobao.org/strip-bom/download/strip-bom-2.0.0.tgz", "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", "requires": { - "is-utf8": "^0.2.0" + "is-utf8": "0.2.1" } }, "strip-eof": { @@ -5778,9 +5778,9 @@ "resolved": "http://registry.npm.taobao.org/stylehacks/download/stylehacks-4.0.1.tgz", "integrity": "sha1-MYZZXQR6sN+BPSE+Uci5TguQEPI=", "requires": { - "browserslist": "^4.0.0", - "postcss": "^7.0.0", - "postcss-selector-parser": "^3.0.0" + "browserslist": "4.3.6", + "postcss": "7.0.7", + "postcss-selector-parser": "3.1.1" }, "dependencies": { "postcss-selector-parser": { @@ -5788,9 +5788,9 @@ "resolved": "http://registry.npm.taobao.org/postcss-selector-parser/download/postcss-selector-parser-3.1.1.tgz", "integrity": "sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU=", "requires": { - "dot-prop": "^4.1.1", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" + "dot-prop": "4.2.0", + "indexes-of": "1.0.1", + "uniq": "1.0.1" } } } @@ -5805,8 +5805,8 @@ "resolved": "http://registry.npm.taobao.org/sver-compat/download/sver-compat-1.5.0.tgz", "integrity": "sha1-PPh9/rTQe0o/FIJ7wYaz/QxkXNg=", "requires": { - "es6-iterator": "^2.0.1", - "es6-symbol": "^3.1.1" + "es6-iterator": "2.0.3", + "es6-symbol": "3.1.1" } }, "svgo": { @@ -5814,20 +5814,20 @@ "resolved": "http://registry.npm.taobao.org/svgo/download/svgo-1.1.1.tgz", "integrity": "sha1-EjhLAzNbzs2Fz6X04zdf7WccuYU=", "requires": { - "coa": "~2.0.1", - "colors": "~1.1.2", - "css-select": "^2.0.0", - "css-select-base-adapter": "~0.1.0", + "coa": "2.0.2", + "colors": "1.1.2", + "css-select": "2.0.2", + "css-select-base-adapter": "0.1.1", "css-tree": "1.0.0-alpha.28", - "css-url-regex": "^1.1.0", - "csso": "^3.5.0", - "js-yaml": "^3.12.0", - "mkdirp": "~0.5.1", - "object.values": "^1.0.4", - "sax": "~1.2.4", - "stable": "~0.1.6", - "unquote": "~1.1.1", - "util.promisify": "~1.0.0" + "css-url-regex": "1.1.0", + "csso": "3.5.1", + "js-yaml": "3.12.0", + "mkdirp": "0.5.1", + "object.values": "1.0.4", + "sax": "1.2.4", + "stable": "0.1.8", + "unquote": "1.1.1", + "util.promisify": "1.0.0" } }, "symbol-tree": { @@ -5840,8 +5840,8 @@ "resolved": "http://registry.npm.taobao.org/through2/download/through2-2.0.5.tgz", "integrity": "sha1-AcHjnrMdB8t9A6lqcIIyYLIxMs0=", "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" + "readable-stream": "2.3.6", + "xtend": "4.0.1" } }, "through2-filter": { @@ -5849,8 +5849,8 @@ "resolved": "http://registry.npm.taobao.org/through2-filter/download/through2-filter-3.0.0.tgz", "integrity": "sha1-cA54bfI2fCyIzYqlvkz5weeDElQ=", "requires": { - "through2": "~2.0.0", - "xtend": "~4.0.0" + "through2": "2.0.5", + "xtend": "4.0.1" } }, "time-stamp": { @@ -5868,8 +5868,8 @@ "resolved": "http://registry.npm.taobao.org/to-absolute-glob/download/to-absolute-glob-2.0.2.tgz", "integrity": "sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=", "requires": { - "is-absolute": "^1.0.0", - "is-negated-glob": "^1.0.0" + "is-absolute": "1.0.0", + "is-negated-glob": "1.0.0" } }, "to-object-path": { @@ -5877,7 +5877,7 @@ "resolved": "http://registry.npm.taobao.org/to-object-path/download/to-object-path-0.3.0.tgz", "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -5885,7 +5885,7 @@ "resolved": "http://registry.npm.taobao.org/kind-of/download/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } @@ -5895,10 +5895,10 @@ "resolved": "http://registry.npm.taobao.org/to-regex/download/to-regex-3.0.2.tgz", "integrity": "sha1-E8/dmzNlUvMLUfM6iuG0Knp1mc4=", "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "regex-not": "1.0.2", + "safe-regex": "1.1.0" } }, "to-regex-range": { @@ -5906,8 +5906,8 @@ "resolved": "http://registry.npm.taobao.org/to-regex-range/download/to-regex-range-2.1.1.tgz", "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" + "is-number": "3.0.0", + "repeat-string": "1.6.1" } }, "to-through": { @@ -5915,7 +5915,7 @@ "resolved": "http://registry.npm.taobao.org/to-through/download/to-through-2.0.0.tgz", "integrity": "sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY=", "requires": { - "through2": "^2.0.3" + "through2": "2.0.5" } }, "tough-cookie": { @@ -5923,8 +5923,8 @@ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" + "psl": "1.1.29", + "punycode": "1.4.1" }, "dependencies": { "punycode": { @@ -5939,7 +5939,7 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", "requires": { - "punycode": "^2.1.0" + "punycode": "2.1.1" } }, "trim": { @@ -5957,7 +5957,7 @@ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "requires": { - "safe-buffer": "^5.0.1" + "safe-buffer": "5.1.2" } }, "tweetnacl": { @@ -5971,7 +5971,7 @@ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", "requires": { - "prelude-ls": "~1.1.2" + "prelude-ls": "1.1.2" } }, "typedarray": { @@ -5984,8 +5984,8 @@ "resolved": "http://registry.npm.taobao.org/uglify-js/download/uglify-js-3.4.9.tgz", "integrity": "sha1-rwLxgMEgfXZDLkc+0koo9KeCuuM=", "requires": { - "commander": "~2.17.1", - "source-map": "~0.6.1" + "commander": "2.17.1", + "source-map": "0.6.1" } }, "unc-path-regex": { @@ -5998,15 +5998,15 @@ "resolved": "http://registry.npm.taobao.org/undertaker/download/undertaker-1.2.0.tgz", "integrity": "sha1-M52kZGJS0ILcN45wgGcpl1DhG0k=", "requires": { - "arr-flatten": "^1.0.1", - "arr-map": "^2.0.0", - "bach": "^1.0.0", - "collection-map": "^1.0.0", - "es6-weak-map": "^2.0.1", - "last-run": "^1.1.0", - "object.defaults": "^1.0.0", - "object.reduce": "^1.0.0", - "undertaker-registry": "^1.0.0" + "arr-flatten": "1.1.0", + "arr-map": "2.0.2", + "bach": "1.2.0", + "collection-map": "1.0.0", + "es6-weak-map": "2.0.2", + "last-run": "1.1.1", + "object.defaults": "1.1.0", + "object.reduce": "1.0.1", + "undertaker-registry": "1.0.1" } }, "undertaker-registry": { @@ -6019,7 +6019,7 @@ "resolved": "https://registry.npmjs.org/unified-lint-rule/-/unified-lint-rule-1.0.3.tgz", "integrity": "sha512-6z+HH3mtlFdj/w3MaQpObrZAd9KRiro370GxBFh13qkV8LYR21lLozA4iQiZPhe7KuX/lHewoGOEgQ4AWrAR3Q==", "requires": { - "wrapped": "^1.0.1" + "wrapped": "1.0.1" } }, "unified-message-control": { @@ -6028,8 +6028,8 @@ "integrity": "sha512-e1dEtN4Z/TvLn/qHm+xeZpzqhJTtfZusFErk336kkZVpqrJYiV9ptxq+SbRPFMlN0OkjDYHmVJ929KYjsMTo3g==", "requires": { "trim": "0.0.1", - "unist-util-visit": "^1.0.0", - "vfile-location": "^2.0.0" + "unist-util-visit": "1.4.0", + "vfile-location": "2.0.3" } }, "union-value": { @@ -6037,10 +6037,10 @@ "resolved": "http://registry.npm.taobao.org/union-value/download/union-value-1.0.0.tgz", "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^0.4.3" + "arr-union": "3.1.0", + "get-value": "2.0.6", + "is-extendable": "0.1.1", + "set-value": "0.4.3" }, "dependencies": { "extend-shallow": { @@ -6048,7 +6048,7 @@ "resolved": "http://registry.npm.taobao.org/extend-shallow/download/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } }, "set-value": { @@ -6056,10 +6056,10 @@ "resolved": "http://registry.npm.taobao.org/set-value/download/set-value-0.4.3.tgz", "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.1", - "to-object-path": "^0.3.0" + "extend-shallow": "2.0.1", + "is-extendable": "0.1.1", + "is-plain-object": "2.0.4", + "to-object-path": "0.3.0" } } } @@ -6079,8 +6079,8 @@ "resolved": "http://registry.npm.taobao.org/unique-stream/download/unique-stream-2.3.1.tgz", "integrity": "sha1-xl0RDppK35psWUiygFPZqNBMvqw=", "requires": { - "json-stable-stringify-without-jsonify": "^1.0.1", - "through2-filter": "^3.0.0" + "json-stable-stringify-without-jsonify": "1.0.1", + "through2-filter": "3.0.0" } }, "unist-util-generated": { @@ -6108,7 +6108,7 @@ "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-1.4.0.tgz", "integrity": "sha512-FiGu34ziNsZA3ZUteZxSFaczIjGmksfSgdKqBfOejrrfzyUy5b7YrlzT1Bcvi+djkYDituJDy2XB7tGTeBieKw==", "requires": { - "unist-util-visit-parents": "^2.0.0" + "unist-util-visit-parents": "2.0.1" } }, "unist-util-visit-parents": { @@ -6116,7 +6116,7 @@ "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-2.0.1.tgz", "integrity": "sha512-6B0UTiMfdWql4cQ03gDTCSns+64Zkfo2OCbK31Ov0uMizEz+CJeAp0cgZVb5Fhmcd7Bct2iRNywejT0orpbqUA==", "requires": { - "unist-util-is": "^2.1.2" + "unist-util-is": "2.1.2" } }, "unquote": { @@ -6129,8 +6129,8 @@ "resolved": "http://registry.npm.taobao.org/unset-value/download/unset-value-1.0.0.tgz", "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" + "has-value": "0.3.1", + "isobject": "3.0.1" }, "dependencies": { "has-value": { @@ -6138,9 +6138,9 @@ "resolved": "http://registry.npm.taobao.org/has-value/download/has-value-0.3.1.tgz", "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" + "get-value": "2.0.6", + "has-values": "0.1.4", + "isobject": "2.1.0" }, "dependencies": { "isobject": { @@ -6190,8 +6190,8 @@ "resolved": "http://registry.npm.taobao.org/util.promisify/download/util.promisify-1.0.0.tgz", "integrity": "sha1-RA9xZaRZyaFtwUXrjnLzVocJcDA=", "requires": { - "define-properties": "^1.1.2", - "object.getownpropertydescriptors": "^2.0.3" + "define-properties": "1.1.3", + "object.getownpropertydescriptors": "2.0.3" } }, "uuid": { @@ -6204,7 +6204,7 @@ "resolved": "http://registry.npm.taobao.org/v8flags/download/v8flags-3.1.2.tgz", "integrity": "sha1-/FzQwidCgYHmwpspkuT48dpeDJ8=", "requires": { - "homedir-polyfill": "^1.0.1" + "homedir-polyfill": "1.0.1" } }, "validate-npm-package-license": { @@ -6212,8 +6212,8 @@ "resolved": "http://registry.npm.taobao.org/validate-npm-package-license/download/validate-npm-package-license-3.0.4.tgz", "integrity": "sha1-/JH2uce6FchX9MssXe/uw51PQQo=", "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" + "spdx-correct": "3.1.0", + "spdx-expression-parse": "3.0.0" } }, "value-or-function": { @@ -6231,9 +6231,9 @@ "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", "requires": { - "assert-plus": "^1.0.0", + "assert-plus": "1.0.0", "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" + "extsprintf": "1.3.0" } }, "vfile-location": { @@ -6246,12 +6246,12 @@ "resolved": "http://registry.npm.taobao.org/vinyl/download/vinyl-2.2.0.tgz", "integrity": "sha1-2FsH2pbkWNJbL/4Z/s6fLKoT7YY=", "requires": { - "clone": "^2.1.1", - "clone-buffer": "^1.0.0", - "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" + "clone": "2.1.2", + "clone-buffer": "1.0.0", + "clone-stats": "1.0.0", + "cloneable-readable": "1.1.2", + "remove-trailing-separator": "1.1.0", + "replace-ext": "1.0.0" } }, "vinyl-fs": { @@ -6259,23 +6259,23 @@ "resolved": "http://registry.npm.taobao.org/vinyl-fs/download/vinyl-fs-3.0.3.tgz", "integrity": "sha1-yFhJQF9nQo/qu71cXb3WT0fTG8c=", "requires": { - "fs-mkdirp-stream": "^1.0.0", - "glob-stream": "^6.1.0", - "graceful-fs": "^4.0.0", - "is-valid-glob": "^1.0.0", - "lazystream": "^1.0.0", - "lead": "^1.0.0", - "object.assign": "^4.0.4", - "pumpify": "^1.3.5", - "readable-stream": "^2.3.3", - "remove-bom-buffer": "^3.0.0", - "remove-bom-stream": "^1.2.0", - "resolve-options": "^1.1.0", - "through2": "^2.0.0", - "to-through": "^2.0.0", - "value-or-function": "^3.0.0", - "vinyl": "^2.0.0", - "vinyl-sourcemap": "^1.1.0" + "fs-mkdirp-stream": "1.0.0", + "glob-stream": "6.1.0", + "graceful-fs": "4.1.15", + "is-valid-glob": "1.0.0", + "lazystream": "1.0.0", + "lead": "1.0.0", + "object.assign": "4.1.0", + "pumpify": "1.5.1", + "readable-stream": "2.3.6", + "remove-bom-buffer": "3.0.0", + "remove-bom-stream": "1.2.0", + "resolve-options": "1.1.0", + "through2": "2.0.5", + "to-through": "2.0.0", + "value-or-function": "3.0.0", + "vinyl": "2.2.0", + "vinyl-sourcemap": "1.1.0" } }, "vinyl-sourcemap": { @@ -6283,13 +6283,13 @@ "resolved": "http://registry.npm.taobao.org/vinyl-sourcemap/download/vinyl-sourcemap-1.1.0.tgz", "integrity": "sha1-kqgAWTo4cDqM2xHYswCtS+Y7PhY=", "requires": { - "append-buffer": "^1.0.2", - "convert-source-map": "^1.5.0", - "graceful-fs": "^4.1.6", - "normalize-path": "^2.1.1", - "now-and-later": "^2.0.0", - "remove-bom-buffer": "^3.0.0", - "vinyl": "^2.0.0" + "append-buffer": "1.0.2", + "convert-source-map": "1.6.0", + "graceful-fs": "4.1.15", + "normalize-path": "2.1.1", + "now-and-later": "2.0.0", + "remove-bom-buffer": "3.0.0", + "vinyl": "2.2.0" } }, "vinyl-sourcemaps-apply": { @@ -6297,7 +6297,7 @@ "resolved": "http://registry.npm.taobao.org/vinyl-sourcemaps-apply/download/vinyl-sourcemaps-apply-0.2.1.tgz", "integrity": "sha1-q2VJ1h0XLCsbh75cUI0jnI74dwU=", "requires": { - "source-map": "^0.5.1" + "source-map": "0.5.7" }, "dependencies": { "source-map": { @@ -6312,7 +6312,7 @@ "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz", "integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=", "requires": { - "browser-process-hrtime": "^0.1.2" + "browser-process-hrtime": "0.1.2" } }, "webidl-conversions": { @@ -6338,9 +6338,9 @@ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" + "lodash.sortby": "4.7.0", + "tr46": "1.0.1", + "webidl-conversions": "4.0.2" } }, "which": { @@ -6348,7 +6348,7 @@ "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "requires": { - "isexe": "^2.0.0" + "isexe": "2.0.0" } }, "which-module": { @@ -6366,8 +6366,8 @@ "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" + "string-width": "1.0.2", + "strip-ansi": "3.0.1" }, "dependencies": { "ansi-regex": { @@ -6380,7 +6380,7 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "requires": { - "number-is-nan": "^1.0.0" + "number-is-nan": "1.0.1" } }, "string-width": { @@ -6388,9 +6388,9 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" } }, "strip-ansi": { @@ -6398,7 +6398,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "2.1.1" } } } @@ -6409,7 +6409,7 @@ "integrity": "sha1-x4PZ2Aeyc+mwHoUWgKk4yHyQckI=", "requires": { "co": "3.1.0", - "sliced": "^1.0.1" + "sliced": "1.0.1" }, "dependencies": { "co": { @@ -6429,7 +6429,7 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", "requires": { - "async-limiter": "~1.0.0" + "async-limiter": "1.0.0" } }, "xml-name-validator": { @@ -6457,18 +6457,18 @@ "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz", "integrity": "sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A==", "requires": { - "cliui": "^4.0.0", - "decamelize": "^1.1.1", - "find-up": "^2.1.0", - "get-caller-file": "^1.0.1", - "os-locale": "^2.0.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^9.0.2" + "cliui": "4.1.0", + "decamelize": "1.2.0", + "find-up": "2.1.0", + "get-caller-file": "1.0.3", + "os-locale": "2.1.0", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "2.1.1", + "which-module": "2.0.0", + "y18n": "3.2.1", + "yargs-parser": "9.0.2" } }, "yargs-parser": { @@ -6476,7 +6476,7 @@ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-9.0.2.tgz", "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", "requires": { - "camelcase": "^4.1.0" + "camelcase": "4.1.0" } } } -- 2.11.0