Loading docs/ds/sparse-table.md +18 −27 Original line number Diff line number Diff line Loading @@ -129,30 +129,21 @@ ST 表能较好的维护“可重复贡献”的区间信息(同时也应满 ## 附录:ST 表求区间 GCD 的时间复杂度分析 上文提过 ST 表查询区间 GCD 的时间复杂度并不比线段树更优,预处理的时间复杂度也不比线段树更劣,这里就要证明这一命题。 在算法运行的时候,可能要经过 $\Theta(\log n)$ 次迭代。每一次迭代都可能会使用 GCD 函数进行递归,令值域为 $w$,GCD 函数的时间复杂度最高是 $\Omega(\log w)$ 的,所以总时间复杂度看似有 $\Omicron(n\log n\log w)$。 理解本段,可能需要具备 [时间复杂度](https://oi-wiki.org/misc/complexity/) 的关于 `势能分析法` 的知识,本段也会作简要说明。 但是,在 GCD 的过程中,每一次递归(除最后一次递归之外)都会使数列中的某个数至少减半,而数列中的数最多减半的次数为 $\log_2 (w^n)=\Theta(n\log w)$ ,所以,GCD 的递归部分最多只会运行 $\Omicron(n\log w)$ 次。再加上循环部分(以及最后一次递归)的 $\Theta(n\log n)$ ,最终时间复杂度则是 $\Omicron(n(\log w+\log x))$,由于可以构造数据使得时间复杂度为 $\Omega(n(\log w+\log x))$ ,所以最终的时间复杂度即为 $\Theta(n(\log w+\log x))$ 。 一系列操作的 `均摊时间复杂度` 是指操作 `实际表现出来` 的时间复杂度。在一系列操作中,可能会有一些操作的耗时更多。例如,在 [单调队列](https://oi-wiki.org/ds/monotonous-queue/) 中,单次操作的时间复杂度可能是 $\Omega(n)$ 的,但是其总时间复杂度却是 $\Omicron(n)$ ,单次操作的时间复杂度是 $\Theta(1)$ 的。因为耗时长的操作所用的时间可以均摊到耗时短的操作所用的时间上,从而获得一个更紧确的时间复杂度的界。 而查询部分的时间复杂度很好分析,考虑最劣情况,即每次询问都询问最劣的一对数,时间复杂度为 $\Theta(\log w)$ 。因此,ST 表维护 “区间GCD” 的时间复杂度为预处理 $\Theta(n(\log n+\log w))$ ,单次查询 $\Theta(\log w)$ 。 需要注意的是,这里的“均摊”不是“平均”。平均时间复杂度即期望时间复杂度,是用来度量 **输入随机** 情况下的算法的时间复杂度的,而均摊时间复杂度的分析一般可以应用在任何一组输入上。 线段树的相应操作是预处理 $Theta(n\log x)$ ,查询 $\Theta(n(\log n+\log x))$ 。 `势能分析法` 就是分析 `均摊时间复杂度` 的一种方法。用时长的操作引起势能的下降,而用时短的操作引起势能的上升,从而将用时长的操作所花费的时间均摊到用时短的操作上了。 这并不是一个严谨的数学论证,更为严谨的附在下方: 接下来就是分析时间复杂度了。先分析预处理部分的时间复杂度: 设 `待考虑数列` 为在预处理 ST 表的时候当前层循环的数列。例如,第零层的数列就是整个数列,第一层的数列就是第零层的数列经过一次迭代之后的数列,即 `st[..][1]` ,我们记为 $A$ 而势能函数就定义为 `待考虑数列` 中所有数的累乘的以二为底的对数。即: $\Phi(A)=\log_2 \prod\limits A_i$ 在一次迭代中,所花费的时间相当于迭代循环所花费的时间与 `GCD` 所花费的时间之和。其中, `GCD` 花费的时间有长有短。最短可能只有两次甚至一次递归,而最长可能有 $O(\log w)$ 次递归。但是, `GCD` 过程中,除最开头一层与最末一层以外,每次递归都会使 `待考虑数列` 中的某个结果至少减半。即, $\Phi(x)$ 会减少至少 $1$ ,该层递归所用的时间可以被势能函数均摊。 同时,我们可以看到, $\Phi(A)$ 的初值最大为 $\log_2 x^n=\Theta(n\log x)$ ,而 $\Phi(A)$ 不增。所以,我们证明了预处理部分的时间复杂度为 $\Omicron(n(\log x+\log n))$ 。另外,可以构造数据使得所用时间为这个时间复杂度,于是证明了它的时间复杂度为 $\Omega(n(\log x+\log n))$ ,最终时间复杂度即为 $\Theta(n(\log x+\log n))$ 。 相应的,可以构造数据证明线段树的时间复杂度为 $\Omega(n\log x)$ ,ST 表的预处理时间复杂度并不比线段树的劣。 而查询部分的时间复杂度很好分析,考虑最劣情况,即每次询问都询问最劣的一对数,时间复杂度为 $\Theta(\log x)$ 。线段树的查询部分需要结合势能函数进行分析,略过不表。 因此,ST 表维护“区间 GCD”的时间复杂度为预处理 $\Theta(n(\log n+\log w))$ ,单次查询 $\Theta(\log w)$ 。 ??? note "更严谨的证明" 理解本段,可能需要具备 [时间复杂度](../misc/complexity/) 的关于 “势能分析法” 的知识。 先分析预处理部分的时间复杂度: 设 “待考虑数列” 为在预处理 ST 表的时候当前层循环的数列。例如,第零层的数列就是原数列,第一层的数列就是第零层的数列经过一次迭代之后的数列,即 `st[1..n][1]` ,我们将其记为 $A$ 。 而势能函数就定义为 “待考虑数列” 中所有数的累乘的以二为底的对数。即: $$\Phi(A)=\log_2\left(\prod\limits_{i=1}^n A_i\right)$$ 在一次迭代中,所花费的时间相当于迭代循环所花费的时间与 GCD 所花费的时间之和。其中, GCD 花费的时间有长有短。最短可能只有两次甚至一次递归,而最长可能有 $O(\log w)$ 次递归。但是, GCD 过程中,除最开头一层与最末一层以外,每次递归都会使 “待考虑数列” 中的某个结果至少减半。即, $\Phi(A)$ 会减少至少 $1$ ,该层递归所用的时间可以被势能函数均摊。 同时,我们可以看到, $\Phi(A)$ 的初值最大为 $\log_2 (w^n)=\Theta(n\log w)$ ,而 $\Phi(A)$ 不增。所以,ST 表预处理部分的时间复杂度为 $\Omicron(n(\log w+\log n))$ 。 Loading
docs/ds/sparse-table.md +18 −27 Original line number Diff line number Diff line Loading @@ -129,30 +129,21 @@ ST 表能较好的维护“可重复贡献”的区间信息(同时也应满 ## 附录:ST 表求区间 GCD 的时间复杂度分析 上文提过 ST 表查询区间 GCD 的时间复杂度并不比线段树更优,预处理的时间复杂度也不比线段树更劣,这里就要证明这一命题。 在算法运行的时候,可能要经过 $\Theta(\log n)$ 次迭代。每一次迭代都可能会使用 GCD 函数进行递归,令值域为 $w$,GCD 函数的时间复杂度最高是 $\Omega(\log w)$ 的,所以总时间复杂度看似有 $\Omicron(n\log n\log w)$。 理解本段,可能需要具备 [时间复杂度](https://oi-wiki.org/misc/complexity/) 的关于 `势能分析法` 的知识,本段也会作简要说明。 但是,在 GCD 的过程中,每一次递归(除最后一次递归之外)都会使数列中的某个数至少减半,而数列中的数最多减半的次数为 $\log_2 (w^n)=\Theta(n\log w)$ ,所以,GCD 的递归部分最多只会运行 $\Omicron(n\log w)$ 次。再加上循环部分(以及最后一次递归)的 $\Theta(n\log n)$ ,最终时间复杂度则是 $\Omicron(n(\log w+\log x))$,由于可以构造数据使得时间复杂度为 $\Omega(n(\log w+\log x))$ ,所以最终的时间复杂度即为 $\Theta(n(\log w+\log x))$ 。 一系列操作的 `均摊时间复杂度` 是指操作 `实际表现出来` 的时间复杂度。在一系列操作中,可能会有一些操作的耗时更多。例如,在 [单调队列](https://oi-wiki.org/ds/monotonous-queue/) 中,单次操作的时间复杂度可能是 $\Omega(n)$ 的,但是其总时间复杂度却是 $\Omicron(n)$ ,单次操作的时间复杂度是 $\Theta(1)$ 的。因为耗时长的操作所用的时间可以均摊到耗时短的操作所用的时间上,从而获得一个更紧确的时间复杂度的界。 而查询部分的时间复杂度很好分析,考虑最劣情况,即每次询问都询问最劣的一对数,时间复杂度为 $\Theta(\log w)$ 。因此,ST 表维护 “区间GCD” 的时间复杂度为预处理 $\Theta(n(\log n+\log w))$ ,单次查询 $\Theta(\log w)$ 。 需要注意的是,这里的“均摊”不是“平均”。平均时间复杂度即期望时间复杂度,是用来度量 **输入随机** 情况下的算法的时间复杂度的,而均摊时间复杂度的分析一般可以应用在任何一组输入上。 线段树的相应操作是预处理 $Theta(n\log x)$ ,查询 $\Theta(n(\log n+\log x))$ 。 `势能分析法` 就是分析 `均摊时间复杂度` 的一种方法。用时长的操作引起势能的下降,而用时短的操作引起势能的上升,从而将用时长的操作所花费的时间均摊到用时短的操作上了。 这并不是一个严谨的数学论证,更为严谨的附在下方: 接下来就是分析时间复杂度了。先分析预处理部分的时间复杂度: 设 `待考虑数列` 为在预处理 ST 表的时候当前层循环的数列。例如,第零层的数列就是整个数列,第一层的数列就是第零层的数列经过一次迭代之后的数列,即 `st[..][1]` ,我们记为 $A$ 而势能函数就定义为 `待考虑数列` 中所有数的累乘的以二为底的对数。即: $\Phi(A)=\log_2 \prod\limits A_i$ 在一次迭代中,所花费的时间相当于迭代循环所花费的时间与 `GCD` 所花费的时间之和。其中, `GCD` 花费的时间有长有短。最短可能只有两次甚至一次递归,而最长可能有 $O(\log w)$ 次递归。但是, `GCD` 过程中,除最开头一层与最末一层以外,每次递归都会使 `待考虑数列` 中的某个结果至少减半。即, $\Phi(x)$ 会减少至少 $1$ ,该层递归所用的时间可以被势能函数均摊。 同时,我们可以看到, $\Phi(A)$ 的初值最大为 $\log_2 x^n=\Theta(n\log x)$ ,而 $\Phi(A)$ 不增。所以,我们证明了预处理部分的时间复杂度为 $\Omicron(n(\log x+\log n))$ 。另外,可以构造数据使得所用时间为这个时间复杂度,于是证明了它的时间复杂度为 $\Omega(n(\log x+\log n))$ ,最终时间复杂度即为 $\Theta(n(\log x+\log n))$ 。 相应的,可以构造数据证明线段树的时间复杂度为 $\Omega(n\log x)$ ,ST 表的预处理时间复杂度并不比线段树的劣。 而查询部分的时间复杂度很好分析,考虑最劣情况,即每次询问都询问最劣的一对数,时间复杂度为 $\Theta(\log x)$ 。线段树的查询部分需要结合势能函数进行分析,略过不表。 因此,ST 表维护“区间 GCD”的时间复杂度为预处理 $\Theta(n(\log n+\log w))$ ,单次查询 $\Theta(\log w)$ 。 ??? note "更严谨的证明" 理解本段,可能需要具备 [时间复杂度](../misc/complexity/) 的关于 “势能分析法” 的知识。 先分析预处理部分的时间复杂度: 设 “待考虑数列” 为在预处理 ST 表的时候当前层循环的数列。例如,第零层的数列就是原数列,第一层的数列就是第零层的数列经过一次迭代之后的数列,即 `st[1..n][1]` ,我们将其记为 $A$ 。 而势能函数就定义为 “待考虑数列” 中所有数的累乘的以二为底的对数。即: $$\Phi(A)=\log_2\left(\prod\limits_{i=1}^n A_i\right)$$ 在一次迭代中,所花费的时间相当于迭代循环所花费的时间与 GCD 所花费的时间之和。其中, GCD 花费的时间有长有短。最短可能只有两次甚至一次递归,而最长可能有 $O(\log w)$ 次递归。但是, GCD 过程中,除最开头一层与最末一层以外,每次递归都会使 “待考虑数列” 中的某个结果至少减半。即, $\Phi(A)$ 会减少至少 $1$ ,该层递归所用的时间可以被势能函数均摊。 同时,我们可以看到, $\Phi(A)$ 的初值最大为 $\log_2 (w^n)=\Theta(n\log w)$ ,而 $\Phi(A)$ 不增。所以,ST 表预处理部分的时间复杂度为 $\Omicron(n(\log w+\log n))$ 。