算法学习笔记(28): 筛法

筛法

冷静分析:CMD 的博客

线性筛

线性筛是个好东西。一般来说,可以在 \(O(n)\) 内处理几乎所有的积性函数。

还可以 \(O(n)\) 找出所有的质数……(废话


杜教筛

放在偏序关系 \((\Z, |)\) 中卷积……

如何快速的求 \(S(n) = \sum_{i = 1}^n f(i)\)

如果能够找到一个函数 \(g\)

\[\begin{aligned} \sum_{i = 1}^n (f * g)(i) &= \sum_{i = 1}^n \sum_{d | i} f(\frac id) g(d) \\ &= \sum_{d = 1}^{n} g(d) \sum_{i = 1}^{\lfloor \frac nd \rfloor} f(i) \\ &= \sum_{d = 1}^{n} g(d) S(\lfloor \frac nd \rfloor) \\ \end{aligned} \]

那么可以得出:

\[g(1)S(n) = \sum_{i = 1}^n (f * g)(i) - \sum_{d = 2}^n g(d) S(\lfloor \frac nd \rfloor) \]

这样我们就可以通过整除分块快速的求出 \(g(1) S(n)\) 了。

当然,需要有很严苛的前提:

  • \(f * g\) 的前缀和是可以快速求的。

  • \(g\) 的前缀和也是可以快速求的。

  • 这里的快速应该是 \(O(1)\),如果是 \(O(\log n)\) 估计也没关系。

其时间复杂度大概是在 \(O(n^{\frac 23})\) 的,如果不预处理小块的话应该是 \(O(n^{\frac 34})\) 的。


例题

  1. 快速求 \(\sum_{i = 1}^n \sum_{j = 1}^n \varphi(\gcd(i, j))\)

  2. 快速求 \(\sum_{i = 1}^n \sum_{j = 1}^n i \cdot j \cdot gcd(i, j)\)

例二中有一个很好的东西,点乘。

其定义有:\((f \cdot g)(n) = f(n) \cdot g(n)\)

\(h\)完全积性函数时,有 \((f \cdot h) * (g \cdot h) = (f * g) \cdot h\)

这里列出一些基本的组合:

  • \(\mu \cdot id_k\)

    \(((\mu \cdot id_k) * id_k)(n) = \sum_{d | n} \mu(d) d^k (\frac nd)^k = n^{k + 1}\sum_{d | n} \mu(d) = \varepsilon(n)\)

  • \(\varphi \cdot id_k\)

    \((\varphi \cdot id_k) * id_k = I\),推导同上。


Min25 筛

本质是对线性筛的扩展……

为了方便,声明一些符号:

  • \(P\) 代表质数集合。

  • \(P_i\) 表示第 \(i\) 个质数。

  • \(minp(i)\) 表示 \(i\) 的最小质因数。

还是求 \(\sum_{i = 1}^n f(i)\)。可以用 Min25 筛需要满足一些条件:

  • \(f(i)\) 是一个低阶多项式。

  • \(f(p^k)\) 可以快速的求解。

现在考虑把每一阶分别计算。

TODO


PN 筛

首先定义 Powerful Number

一个数是 Powerful Number 当其质因数分解中 \(n = \prod_{i = 1}^k p_i^{c_i}\)\(\forall i(c_i > 1)\)

由于一个 Powerful Number 可以表示为一个数的平方和一个数的立方,所以 \(n\) 以内的 PN 数一共有 \(O(\sqrt n)\) 个。

现在假设要求 \(F(n) = \sum_{i = 1}^n f(i)\)

需要找到函数 \(g\) 满足:

  • 为积性函数。

  • 易求前缀和。

  • 在质数 \(p\) 处取值与原函数相等:\(f(p) = g(p)\)

\(G(n) = \sum_{i = 1}^n g(n)\)

先构造函数 \(h\) 满足 \(h * g = f\)

于是将卷积展开:

\[\begin{aligned} F(n) &= \sum_{i = 1}^n f(i) = \sum_{i = 1}^n \sum_{d | i} h(d) g(\frac id) \\ &= \sum_{d = 1}^{n} \sum_{i = 1}^{\lfloor \frac nd \rfloor} h(d) g(i) \\ &= \sum_{d = 1}^n h(d) \sum_{i = 1}^{\lfloor \frac nd \rfloor} g(i) \\ &= \sum_{d = 1}^n h(d) G(\lfloor \frac nd \rfloor) \end{aligned} \]

怎么可以套数论分块?

由于积性函数相乘任然为积性函数,所以可知 \(h(1) = 1\)

所以对于质数 \(p\),有 \(f(p) = g(1)h(p) + g(p)h(1) = h(p) + g(p)\)

由于 \(f(p) = g(p)\),所以可知 \(h(p) = 0\)

根据积性函数的性质,可以推导出 \(h(n)\) 只在 \(n\)PN 数的时候才可能不为 \(0\)

所以原式变为:

\[F(n) = \sum_{\text{d is PN}}^{n} h(d) G(\lfloor \frac nd \rfloor) \]

这样,我们只需要求出 \(O(\sqrt n)\)\(h(d)\) 的取值,以及快速算出 \(G(x)\) 的取值即可。

那么我们考虑如何求解 \(h(d)\)

根据 \(f = g * h\)

\[f(p^c) = \sum_{i = 0}^c g(p^i)h(p^{c - i}) \]

稍微移项可以得到:

\[g(1)h(p^c) = f(p^c) - \sum_{i = 1}^c g(p^i)h(p^{c - i}) \\ \downarrow \\ h(p^c) = f(p^c) - \sum_{i = 1}^c g(p^i)h(p^{c - i}) \]

于是可以枚举所有的 \(p\) 以及 \(c\) 求出 \(h(p^c)\) 即可。

此时 \(p\) 的个数视作 \(O(\sqrt n)\),而 \(c\) 的界为 \(O(\log n)\),有由于是 \(\log n\) 转移,所以这部分复杂度为 \(O(\sqrt n \log n^2)\),而且这个上界很宽松。

但是如果我们直接推导出 \(h(p^c)\) 仅与 \(p^c\) 有关的计算公式,那么可能更加优秀,复杂度一般为 \(O(\sqrt n \log n)\)

所以总的复杂度一般为 \(O(\sqrt n + \sqrt n \log n) = O(\sqrt n \log n)\)。非常的优秀。

然而根据 CMD 的博客,硬上的复杂度实际上是 \(O(\sqrt n / \log n)\)……更加优秀了。

不过有些情况下,利用 PN 筛可能需要套一个杜教筛,毕竟我们除了 \(h(p^c)\) 还需要求出 \(G(x)\) 的前缀和。

此时需要加上杜教筛的复杂度,可以认为是 \(O(n^{\frac 23})\) 的。

posted @ 2023-07-26 21:51  jeefy  阅读(27)  评论(0编辑  收藏  举报