浅谈高级筛法

· · 算法·理论

修复:

  1. \text{[update 2025/10/20 17:18] 修改一处文本,但不影响理解}
  2. \text{[update 2025/10/20 18:17] 修改三处标题,但不影响理解}

Parts1: Min_25:

简单来说,Min_25 是一种快速求一个积性函数 f(x) 前缀和的筛法,对于这个 f(x), 我们有以下要求和限制:

那么到底怎么做呢,我们现在来说。首先,我们考虑将质数部分先处理掉,定义一个辅助函数 g(n,j), 其定义为在埃拉托斯特尼筛法,也就是埃氏筛中未被前 j 个质数筛掉(即最小质因子 > p_j)的数的 f'(x) 之和,这里为了后面的讲解,使用一个完全积性函数 f'(x) 近似原函数 f(x) 在质数位置的值。现在我们容易得出,对于 g(n,0) 有:

g(n,0) = \sum_{i=2}^n f'(i)

考虑如果 f'(i) 是一个简单多项式的化,我们可以直接用公式求和。接下来对于所有 g(n,j) 我们考虑从 g(n,0) 推广得出以下递推式:

g(n, j) = \begin{cases} g(n, j-1) - f'(p_j) \cdot \left[ g\left( \left\lfloor \frac{n}{p_j} \right\rfloor, j-1 \right) - \sum_{i=1}^{j-1} f'(p_i) \right], & \text{If } p_j^2 \le n \\ g(n, j-1), & \text{If } p_j^2 > n \end{cases}

对于 g(n,j) 的上限,我们认为 j 应该尽可能取到一个与 \sqrt{n} 最接近且严格大于其的质数,然后我们就会发现这个极限的 g(n,j) 就是对于所有 p \le np 为质数情况下,f'(p) 之和,搞定!接下来考虑计算合数部分,我们定义一个 s(n,i) 表示所有最小质因子大于 p_i 的数的 f(i) 之和。那么我们的目标就是计算 s(n,0),最后加上 f(1) (如果需要的话), 我们考虑 s(n,i) 递归式为

S(n,i) = (g(n,j_{\max}) - \sum_{j = 1}^j f(p_j)) + \sum_{k > i} \sum_{e \ge 1} [f(p_k^e) \cdot (S(\lfloor \frac{n}{p^e_k}\rfloor , k) + [e > 1])]

其中我们计算的是所有大于 p_i 的质数的 f(p) 的和,后面的那个一长串二重求和是枚举合数的最小质因子 p_k,以及其指数。然后 S(\lfloor \frac{n}{p^e_k}\rfloor , k) 表示递归计算除去最小质因子 p_k^e 后剩余部分(其最小质因子大于 p_k) 的 f 值。然后后面的 [e > 1] 是一个指示函数,主要是考虑 p_k^e 本身用的。

递归边界为 n < p_i 时满足 s(n,i) = 0, 最终就是 \sum_{i = 1}^n f(i) = s(n,0) + f(1)

现在我们考虑证明 Min_25 时间复杂度,先证明对于所有 S(m,i), 其中 m 只有最多 \sqrt{n} 种取值,原因非常简单,在计算 S(n,i) 的时候,参数 m 总是形如 \lfloor \frac{n}{k} \rfloor,就是数论分块的证明。当然这个是小菜,我们接下来质数分布的估计,需要前置知识切比雪夫定理,也就是我们规定对于第 k 个质数 p_k \sim k \ln k\pi(n) \sim \frac{n}{\ln n}。这个就是定理,非常简单,而且根据上面的递推式,也很好证明 S(m,i) 所有有意义的 i 满足 p_i \le \sqrt{m}

然后现在我们说, Min_25 时间复杂度为 O(\frac{n^{\frac{3}{4}}}{\log n}) 级别的,我们分析递归计算 S(m,i) 的总代价,我们定义 V = \{\left\lfloor \frac{n}{k} \right\rfloor : 1 \le k \le n\}, 然后根据 p_im 的大小关系,分为:

现在我们逐步分析:

T(m, i) = 1 + \sum_{\substack{k > i \\ p_k \le \sqrt{m}}} T(\lfloor \frac{m}{p_k} \rfloor, k)

但是我们关心的时 T(n,0),可以尝试使用积分近似,有:

\sum_{\substack{m \in V \\ m \ge n^{\frac{1}{2}}}} [\pi(\sqrt{m}) - \pi(m^{\frac{1}{4}})] \sim \sum_{\substack{m \in V \\ m \ge n^{\frac{1}{2}}}} \left[ \frac{2\sqrt{m}}{\log m} - \frac{4m^{\frac{1}{4}}}{\log m} \right]

考虑到 m 较为密集,用积分近似求和:

\int_{n^{\frac{1}{2}}}^n \left( \frac{2\sqrt{x}}{\log x} - \frac{4x^{\frac{1}{4}}}{\log x} \right) \frac{dx}{x}

这里 \frac{dx}{x} 是因为 m 的取值密度约为 \frac{dm}{m}, 然后考虑计算:

\int_{n^{\frac{1}{2}}}^n \frac{2\sqrt{x}}{x\log x} dx = \int_{n^{\frac{1}{2}}}^n \frac{2}{x^{\frac{1}{2}}\log x} dx

然后令 t = \log xdx = e^tdt 则:

= \int_{\frac{1}{2}\log n}^{\log n} \frac{2e^{\frac{t}{2}}}{t} dt \sim \frac{4n^{\frac{1}{2}}}{\log n}

然后后面这一项太小了,直接不看,因此我们估计时间复杂度在 O(\frac{n^{\frac{3}{4}}}{\log n}), 当然这个不是准确值,也有别的说法,但是大概在这个量级是没错的。

Part 2: Meissel-Lehmer

严格意义上来说,Meissel-Lehmer 没有 Min_25 那么实用,但是还是很棒的,我们来详细说一下,Meissel-Lehmer 是求:

\pi(x) = \#\{p \le x | p \text{ is prime}\}

你可能说,哎呀这个筛法不是轻轻松松吗,实则要是我 n 大一点点直接原地升天,这个时候,我们就要升级算法了,我们来说一下算法流程,首先,定义:

\phi(x, a) = \#\{n \le x : p \mid n \implies p > p_a\}

其中 p_a 表示第 a 个素数,其中 p_1 = 2。换句话说,\phi(x,a)1 \dots x 中所有不被前 a 个素数整除的正整数的个数,即:用埃氏筛筛掉前 a 个素数后剩下的数的个数,和上面 Min_25 是差不多的(但是就是要换个说法,主要是写作时间线和参考的文献不太一样,就有些问题多多谅解)显然,这些数包括:

所以容易推导得到:

\pi(x) = \phi(x, a) + a - 1, \quad \text{If } p_a \le \sqrt{x} < p_{a+1}.

接下来我们就要来推导 \phi 的公式了,你只需要记住有:

\phi(x, a) = \phi(x, a-1) - \phi\left( \frac{x}{p_a}, a-1 \right)

a-1a 时,我们筛掉那些不被前 a-1 个素数整除但能被 p_a 整除的数。\ 这些数可以写成 p_a \cdot m,其中 m 不被前 a-1 个素数整除,且 p_a m \le x,即 m \le \lfloor \frac{x}{p_a} \rfloor。\ 满足 m 不被前 a-1 个素数整除的个数正是 \phi\left( \frac{x}{p_a}, a-1 \right)

但是先需要定义 \phi(x,0) = 0, 也就是没筛任何数的时候,所有正整数留下。

但是直接递归计算 \phi 还是无法令人接受,但是我们有一个非常关键的观察:

定义 P_k(x,a) 为:

P_k(x, a) = \#\{ n \le x : n = q_1 q_2 \dots q_k,\ q_1 > q_2 > \dots > q_k > p_a \}

a = \pi(x^{\frac{1}{3}})b = \pi(\sqrt{x})

因为每个不被前 a 个素数整除的数 \le 1 且所有素数大于 p_a

此时我们容易写出:

\phi(x, a) = 1 + \sum_{k=1}^\infty P_k(x, a)

因为每个不被前 k 个素数整除的 >1 的数,其所有素因子 > p_a,且素因子个数 k \geq 1

显然我们可以打个表:

然后瞎猜有:

\phi(x, a) = 1 + [\pi(x) - a] + P_2(x, a) + P_3(x, a) + \dots

接下来我们分开处理,取 a = \pi(x^{\frac{1}{3}}), 则:

p_{a+1} > x^{\frac{1}{3}}

然后对于 k \geq 3 最小 P_3 对应 p_{a+1} p_{a+2} p_{a+3} > (x^{\frac{1}{3}})^3 = x 因此:

P_k(x, a) = 0 \quad \text{If } k \ge 3

所以:

\phi(x, a) = 1 + \pi(x) - a + P_2(x, a)

整理以后:

\pi(x) = \phi(x, a) + a - 1 - P_2(x, a) \tag{1}

然后考虑固定第 i 个素数,其中 a < i \le b = \pi(\sqrt{x}),有 p_j > p_i 使得 p_i p_j \le x 也就是 p_j \le x/p_i, 容易写出这样的 p_j 一共有:

\pi\left( \frac{x}{p_i} \right) - \pi(p_i)

个,但是 \pi(p_i) = i,由此:

P_2(x, a) = \sum_{i=a+1}^{b} \left[ \pi\left( \frac{x}{p_i} \right) - i \right] \tag{2}

最后,将 (1) 和 (2) 写出来,就变成了:

\pi(x) = \phi(x, \pi(\frac{1}{3})) + \pi(\frac{1}{3}) - 1 - \sum_{i=\pi(x^{\frac{1}{3}})+1}^{\pi(\sqrt{x})} \left[ \pi\left( \frac{x}{p_i} \right) - i \right]

计算步骤很简单,预处理素数到 \sqrt{x} 然后用 DP 或别的递归方法计算 \phi(x,a), 然后计算 P_2 带入求和得到 \pi(x)

现在是时间复杂度证明时间!

定义计算 \phi(x,a) 时出现的不同第一个参数的集合为:

S = \left\{ \left\lfloor \frac{x}{n} \right\rfloor : n = p_{i_1} p_{i_2} \cdots p_{i_k},\ i_1 > i_2 > \cdots > i_k,\ n \le x,\ k \ge 0 \right\}.

每个 n 是前 a 个素数中若干个的乘积(无平方因子)。

有引理如 |S| = O\left( \frac{x^{\frac{2}{3}}}{\log^2 x} \right),证明很简单,如下:

Sn \le x^{\frac{1}{3}}n > x^{\frac{1}{3}} 分成两部分。

综合两部分,|S| = O\left( \frac{x^{\frac{2}{3}}}{\log^2 x} \right)

然后又有引理有:计算 \phi(m, a) 对所有 m \in S 的时间为 O\left( \frac{x^{\frac{2}{3}}}{\log^2 x} \cdot a \right)

证明依旧简单,但是因为篇幅问题,不证明了。接下来证明 P_2 的时间复杂度:

P_2 = \sum_{i=a+1}^{b} \left[ \pi\left( \frac{x}{p_i} \right) - i \right].

其中项数 b-a = \pi(\sqrt{x}) - \pi(x^{1/3}) = \Theta\left( \frac{\sqrt{x}}{\log x} \right)

需要计算每个 \pi(\frac{x}{p_i}), 其中 p_i \ge x^{\frac{1}{3}}, 然后考虑预计算 \pi(m) 对于所有 m \le x^{\frac{2}{3}} 可以使用埃氏筛完成,因此 P_2 的求和耗时为:

O\left( \frac{\sqrt{x}}{\log x} \right).

接下来汇总:

注意到瓶颈过高,要优化(Lagarias–Miller–Odlyzko 1985) : 通过只保留 m = \lfloor \frac{x}{t} \rfloor 对于 t \le x^{\frac{1}{3}}, 则计算 \phi 的时间为:

O(a \cdot |S|) = O\left( \frac{x^{\frac{1}{3}}}{\log x} \cdot x^{\frac{1}{3}} \right) = O\left( \frac{x^{\frac{2}{3}}}{\log x} \right)

预计算一些小 m\phi 可以在 O(x^{\frac{2}{3}} \log \log x) 内完成。

最后汇总得到:

O\left( \frac{x^{\frac{2}{3}}}{\log x} + x^{\frac{2}{3}} \log \log x + \frac{\sqrt{x}}{\log x} \right) = O\left( \frac{x^{\frac{2}{3}}}{\log x} \right).

如果你还是优化大佬的话,兴许可以来到:

O\left( \frac{x^{\frac{2}{3}}}{\log^2 x} \right)

还是比 Min_25 快多了。

Part3: Powerful Number 筛

Powerful Number 筛,我们也称作 PN 筛,是最近几年才出现的一种非常厉害的筛法。

定义:正整数 n 为 Powerful Number 当且仅当 n 的所有质因子 p 都有 p^2 \mid n

性质有下:

那么这玩意对我筛 f(n) 前缀和有什么用啊,其核心思路在于我们需要先找一个辅助的函数 g(n) 满足:

看到这个辅助函数,也许你就会想到卷积,确实,定义 h 满足:

f = g \times h \quad \Leftrightarrow \quad f(n) = \sum_{d|n} g(d) h\left(\frac{n}{d}\right)

也就是狄利克雷卷积,其中 h 必须为积性函数。

然后考虑从 h 入手,先计算 h(p^k)

一条关键的结论:h(p)0 对于所有素数 p 成立。质数部分就算完了。

然后考虑 h0 值和 PN 的关系,由于 h 为积性函数且 h(p) = 0p 为质数下。那么对于包含一次质因子的 n 都有 h(n) = 0, 这个非常好证明。

然后我们就要捣鼓前缀和了,前方高能!

f = g \times h

f(n) = \sum_{d|n} g(d) h\left(\frac{n}{d}\right)

带入求前缀和:

S_f(n) = \sum_{i=1}^n f(i) = \sum_{i=1}^n \sum_{d|i} g(d) h\left(\frac{i}{d}\right)

k = \frac{i}{d}i = dkd \mid i,交换求和:

S_f(n) = \sum_{d=1}^n g(d) \sum_{k=1}^{\lfloor \frac{n}{d} \rfloor} h(k)

由于 h(k) 仅在 k 是 PN 时非 0,则我们可以尝试枚举所有可能的 k,从原始的二层求和展开:

S_f(n) = \sum_{i=1}^n \sum_{d|i} g(d) h\left(\frac{i}{d}\right)

固定 k 以后:

S_f(n) = \sum_{k=1}^n h(k) \sum_{d=1}^{\lfloor \frac{n}{k} \rfloor} g(d) = \sum_{k=1}^n h(k) \cdot G\left(\left\lfloor \frac{n}{k} \right\rfloor\right)

根据我们上面的性质有:

S_f(n) = \sum_{\substack{k=1 \\ k \text{ is PN}}}^n h(k) \cdot G\left(\left\lfloor \frac{n}{k} \right\rfloor\right)

现在开始,PN 已经被我们推导完了核心部分!

计算办法很简单,由卷积关系在 n = p^e 处:

f(p^e) = \sum_{i=0}^e g(p^i) h(p^{e-i})

分离 i = 0 项:

f(p^e) = g(1)h(p^e) + \sum_{i=1}^e g(p^i) h(p^{e-i})

由于 g(1) = 1,得:

h(p^e) = f(p^e) - \sum_{i=1}^e g(p^i) h(p^{e-i})

所以算法的步骤就是:

又来到了时间复杂度分析的部分,但是由于这个过于简单,就随便写一下,你只需要知道 PN 个数是 \sqrt{n} 的大致就知道时间复杂度了。

References:

[1].《Min_25 筛学习笔记》, Luogu 用户 @hzoi_liuchang

[2]. Oi-Wiki (Min_25)(Powerful Number)

[3].《一些特殊的数论函数求和问题》(非原文)