专栏文章

浅谈高级筛法

算法·理论参与者 8已保存评论 10

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
10 条
当前快照
1 份
快照标识符
@minmlzak
此快照首次捕获于
2025/12/02 04:53
3 个月前
此快照最后确认于
2025/12/02 04:53
3 个月前
查看原文

修复:

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

Parts1: Min_25:

简单来说,Min_25 是一种快速求一个积性函数 f(x)f(x) 前缀和的筛法,对于这个 f(x)f(x), 我们有以下要求和限制:
  • 首先对于 f(p)f(p) 其中 pp 为质数,可以被表示为低次多项式或者可以快速求出。
  • 其次对于 f(pk)f(p^k) 其中 pp 为质数,也能够快速计算。
那么到底怎么做呢,我们现在来说。首先,我们考虑将质数部分先处理掉,定义一个辅助函数 g(n,j)g(n,j), 其定义为在埃拉托斯特尼筛法,也就是埃氏筛中未被前 jj 个质数筛掉(即最小质因子 > pjp_j)的数的 f(x)f'(x) 之和,这里为了后面的讲解,使用一个完全积性函数 f(x)f'(x) 近似原函数 f(x)f(x) 在质数位置的值。现在我们容易得出,对于 g(n,0)g(n,0) 有:
g(n,0)=i=2nf(i)g(n,0) = \sum_{i=2}^n f'(i)
考虑如果 f(i)f'(i) 是一个简单多项式的化,我们可以直接用公式求和。接下来对于所有 g(n,j)g(n,j) 我们考虑从 g(n,0)g(n,0) 推广得出以下递推式:
g(n,j)={g(n,j1)f(pj)[g(npj,j1)i=1j1f(pi)],If pj2ng(n,j1),If pj2>ng(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)g(n,j) 的上限,我们认为 jj 应该尽可能取到一个与 n\sqrt{n} 最接近且严格大于其的质数,然后我们就会发现这个极限的 g(n,j)g(n,j) 就是对于所有 pnp \le npp 为质数情况下,f(p)f'(p) 之和,搞定!接下来考虑计算合数部分,我们定义一个 s(n,i)s(n,i) 表示所有最小质因子大于 pip_i 的数的 f(i)f(i) 之和。那么我们的目标就是计算 s(n,0)s(n,0),最后加上 f(1)f(1) (如果需要的话), 我们考虑 s(n,i)s(n,i) 递归式为
S(n,i)=(g(n,jmax)j=1jf(pj))+k>ie1[f(pke)(S(npke,k)+[e>1])]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])]
其中我们计算的是所有大于 pip_i 的质数的 f(p)f(p) 的和,后面的那个一长串二重求和是枚举合数的最小质因子 pkp_k,以及其指数。然后 S(npke,k)S(\lfloor \frac{n}{p^e_k}\rfloor , k) 表示递归计算除去最小质因子 pkep_k^e 后剩余部分(其最小质因子大于 pkp_k) 的 ff 值。然后后面的 [e>1][e > 1] 是一个指示函数,主要是考虑 pkep_k^e 本身用的。
递归边界为 n<pin < p_i 时满足 s(n,i)=0s(n,i) = 0, 最终就是 i=1nf(i)=s(n,0)+f(1)\sum_{i = 1}^n f(i) = s(n,0) + f(1)

现在我们考虑证明 Min_25 时间复杂度,先证明对于所有 S(m,i)S(m,i), 其中 mm 只有最多 n\sqrt{n} 种取值,原因非常简单,在计算 S(n,i)S(n,i) 的时候,参数 mm 总是形如 nk\lfloor \frac{n}{k} \rfloor,就是数论分块的证明。当然这个是小菜,我们接下来质数分布的估计,需要前置知识切比雪夫定理,也就是我们规定对于第 kk 个质数 pkklnkp_k \sim k \ln kπ(n)nlnn\pi(n) \sim \frac{n}{\ln n}。这个就是定理,非常简单,而且根据上面的递推式,也很好证明 S(m,i)S(m,i) 所有有意义的 ii 满足 pimp_i \le \sqrt{m}
然后现在我们说, Min_25 时间复杂度为 O(n34logn)O(\frac{n^{\frac{3}{4}}}{\log n}) 级别的,我们分析递归计算 S(m,i)S(m,i) 的总代价,我们定义 V={nk:1kn}V = \{\left\lfloor \frac{n}{k} \right\rfloor : 1 \le k \le n\}, 然后根据 pip_imm 的大小关系,分为:
  • Case 1: pim14p_i \le m^{\frac{1}{4}}
  • Case 2: m14<pinm^{\frac{1}{4}} < p_i \le \sqrt{n}
现在我们逐步分析:
  • 对于 Case 1 的,我们有:首先,根据递归式,我们知道 mm 的取值只有 n\sqrt{n} 中(定理 1),然后,对于所有 mm, 满足 pim14p_i \le m^{\frac{1}{4}}ii 的个数为 π(m14)=O(m14logn)\pi(m^{\frac{1}{4}}) = O(\frac{m^{\frac{1}{4}}}{\log n}) 大概是这个量级的,这个十个粗略估计的结果,但是也足以判断 Case 1 是一个小部分,我们忽略不计。
  • 接下来对于 Case 2 这个关键的地方,我们做以下事情:首先由递归式可以快速推导出我们在考虑上面的东西时,只需要考虑 e=1e = 1 的情况,因此对于每个 S(m,i)S(m,i) (Case 2), 产生的调用总次数为 π(m)i\pi(\sqrt{m}) - i 量级。现在将计算总量。我们令 T(m,i)T(m,i) 表示以 S(m,i)S(m,i) 为根的递归子树中的状态数,我们有:
T(m,i)=1+k>ipkmT(mpk,k)T(m, i) = 1 + \sum_{\substack{k > i \\ p_k \le \sqrt{m}}} T(\lfloor \frac{m}{p_k} \rfloor, k)
但是我们关心的时 T(n,0)T(n,0),可以尝试使用积分近似,有:
mVmn12[π(m)π(m14)]mVmn12[2mlogm4m14logm]\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]
考虑到 mm 较为密集,用积分近似求和:
n12n(2xlogx4x14logx)dxx\int_{n^{\frac{1}{2}}}^n \left( \frac{2\sqrt{x}}{\log x} - \frac{4x^{\frac{1}{4}}}{\log x} \right) \frac{dx}{x}
这里 dxx\frac{dx}{x} 是因为 mm 的取值密度约为 dmm\frac{dm}{m}, 然后考虑计算:
n12n2xxlogxdx=n12n2x12logxdx\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=logxt = \log xdx=etdtdx = e^tdt 则:
=12lognlogn2et2tdt4n12logn= \int_{\frac{1}{2}\log n}^{\log n} \frac{2e^{\frac{t}{2}}}{t} dt \sim \frac{4n^{\frac{1}{2}}}{\log n}
然后后面这一项太小了,直接不看,因此我们估计时间复杂度在 O(n34logn)O(\frac{n^{\frac{3}{4}}}{\log n}), 当然这个不是准确值,也有别的说法,但是大概在这个量级是没错的。

Part 2: Meissel-Lehmer

严格意义上来说,Meissel-Lehmer 没有 Min_25 那么实用,但是还是很棒的,我们来详细说一下,Meissel-Lehmer 是求:
π(x)=#{pxp is prime}\pi(x) = \#\{p \le x | p \text{ is prime}\}
你可能说,哎呀这个筛法不是轻轻松松吗,实则要是我 nn 大一点点直接原地升天,这个时候,我们就要升级算法了,我们来说一下算法流程,首先,定义:
ϕ(x,a)=#{nx:pn    p>pa}\phi(x, a) = \#\{n \le x : p \mid n \implies p > p_a\}
其中 pap_a 表示第 aa 个素数,其中 p1=2p_1 = 2。换句话说,ϕ(x,a)\phi(x,a)1x1 \dots x 中所有不被前 aa 个素数整除的正整数的个数,即:用埃氏筛筛掉前 aa 个素数后剩下的数的个数,和上面 Min_25 是差不多的(但是就是要换个说法,主要是写作时间线和参考的文献不太一样,就有些问题多多谅解)显然,这些数包括:
  • 所有大于 pap_a 的素数(x\le x)。
  • 以及 11
所以容易推导得到:
π(x)=ϕ(x,a)+a1,If pax<pa+1.\pi(x) = \phi(x, a) + a - 1, \quad \text{If } p_a \le \sqrt{x} < p_{a+1}.
接下来我们就要来推导 ϕ\phi 的公式了,你只需要记住有:
ϕ(x,a)=ϕ(x,a1)ϕ(xpa,a1)\phi(x, a) = \phi(x, a-1) - \phi\left( \frac{x}{p_a}, a-1 \right)
a1a-1aa 时,我们筛掉那些不被前 a1a-1 个素数整除但能被 pap_a 整除的数。
这些数可以写成 pamp_a \cdot m,其中 mm 不被前 a1a-1 个素数整除,且 pamxp_a m \le x,即 mxpam \le \lfloor \frac{x}{p_a} \rfloor
满足 mm 不被前 a1a-1 个素数整除的个数正是 ϕ(xpa,a1)\phi\left( \frac{x}{p_a}, a-1 \right)
但是先需要定义 ϕ(x,0)=0\phi(x,0) = 0, 也就是没筛任何数的时候,所有正整数留下。
但是直接递归计算 ϕ\phi 还是无法令人接受,但是我们有一个非常关键的观察:
定义 Pk(x,a)P_k(x,a) 为:
Pk(x,a)=#{nx:n=q1q2qk, q1>q2>>qk>pa}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=π(x13)a = \pi(x^{\frac{1}{3}})b=π(x)b = \pi(\sqrt{x})
因为每个不被前 aa 个素数整除的数 1\le 1 且所有素数大于 pap_a
此时我们容易写出:
ϕ(x,a)=1+k=1Pk(x,a)\phi(x, a) = 1 + \sum_{k=1}^\infty P_k(x, a)
因为每个不被前 kk 个素数整除的 >1>1 的数,其所有素因子 >pa> p_a,且素因子个数 k1k \geq 1
显然我们可以打个表:
  • P1(x,a)=π(x)aP_1(x, a) = \pi(x) - a
  • P2(x,a)=#{pqx:p>q>pa}P_2(x, a) = \#\{ pq \le x : p > q > p_a \}
  • P3(x,a)=#{pqrx:p>q>r>pa}P_3(x, a) = \#\{ pqr \le x : p > q > r > p_a \}
然后瞎猜有:
ϕ(x,a)=1+[π(x)a]+P2(x,a)+P3(x,a)+\phi(x, a) = 1 + [\pi(x) - a] + P_2(x, a) + P_3(x, a) + \dots
接下来我们分开处理,取 a=π(x13)a = \pi(x^{\frac{1}{3}}), 则:
pa+1>x13p_{a+1} > x^{\frac{1}{3}}
然后对于 k3k \geq 3 最小 P3P_3 对应 pa+1pa+2pa+3>(x13)3=xp_{a+1} p_{a+2} p_{a+3} > (x^{\frac{1}{3}})^3 = x 因此:
Pk(x,a)=0If k3P_k(x, a) = 0 \quad \text{If } k \ge 3
所以:
ϕ(x,a)=1+π(x)a+P2(x,a)\phi(x, a) = 1 + \pi(x) - a + P_2(x, a)
整理以后:
π(x)=ϕ(x,a)+a1P2(x,a)(1)\pi(x) = \phi(x, a) + a - 1 - P_2(x, a) \tag{1}
然后考虑固定第 ii 个素数,其中 a<ib=π(x)a < i \le b = \pi(\sqrt{x}),有 pj>pip_j > p_i 使得 pipjxp_i p_j \le x 也就是 pjx/pip_j \le x/p_i, 容易写出这样的 pjp_j 一共有:
π(xpi)π(pi)\pi\left( \frac{x}{p_i} \right) - \pi(p_i)
个,但是 π(pi)=i\pi(p_i) = i,由此:
P2(x,a)=i=a+1b[π(xpi)i](2)P_2(x, a) = \sum_{i=a+1}^{b} \left[ \pi\left( \frac{x}{p_i} \right) - i \right] \tag{2}
最后,将 (1) 和 (2) 写出来,就变成了:
π(x)=ϕ(x,π(13))+π(13)1i=π(x13)+1π(x)[π(xpi)i]\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]
计算步骤很简单,预处理素数到 x\sqrt{x} 然后用 DP 或别的递归方法计算 ϕ(x,a)\phi(x,a), 然后计算 P2P_2 带入求和得到 π(x)\pi(x)

现在是时间复杂度证明时间!
定义计算 ϕ(x,a)\phi(x,a) 时出现的不同第一个参数的集合为:
S={xn:n=pi1pi2pik, i1>i2>>ik, nx, k0}.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\}.
每个 nn 是前 aa 个素数中若干个的乘积(无平方因子)。
有引理如 S=O(x23log2x)|S| = O\left( \frac{x^{\frac{2}{3}}}{\log^2 x} \right),证明很简单,如下:
SSnx13n \le x^{\frac{1}{3}}n>x13n > x^{\frac{1}{3}} 分成两部分。
  • nx13n \le x^{\frac{1}{3}},则 xnx23\lfloor \frac{x}{n} \rfloor \ge x^{\frac{2}{3}},且不同的 nn 的数量不超过 x13x^{\frac{1}{3}},因此对应的 mm 的数量 x13\le x^{\frac{1}{3}}
  • n>x13n > x^{\frac{1}{3}},则 m=xn<x23m = \lfloor \frac{x}{n} \rfloor < x^{\frac{2}{3}}
    对于固定的 mmn=xmn = \lfloor \frac{x}{m} \rfloor 唯一确定,因此这样的 mm 的数量 x23\le x^{\frac{2}{3}}
    但需注意 nn 必须是无平方因子数且素因子来自前 aa 个素数。
    m=xnm = \lfloor \frac{x}{n} \rfloor,则 nxmn \approx \frac{x}{m}
    mx23m \le x^{\frac{2}{3}} 时,nx13n \ge x^{\frac{1}{3}}
    对于 mx12m \le x^{\frac{1}{2}}nx12n \ge x^{\frac{1}{2}},这样的 nn 的数量由大素数积控制,可用组合数上界:
    不超过 (ar)\binom{a}{r} 对某个 rr,且 nx13n \ge x^{\frac{1}{3}}rr 较大,总数经细致平衡可得 O(x23log2x)O\left( \frac{x^{\frac{2}{3}}}{\log^2 x} \right)
综合两部分,S=O(x23log2x)|S| = O\left( \frac{x^{\frac{2}{3}}}{\log^2 x} \right)
然后又有引理有:计算 ϕ(m,a)\phi(m, a) 对所有 mSm \in S 的时间为 O(x23log2xa)O\left( \frac{x^{\frac{2}{3}}}{\log^2 x} \cdot a \right)
证明依旧简单,但是因为篇幅问题,不证明了。接下来证明 P2P_2 的时间复杂度:
P2=i=a+1b[π(xpi)i].P_2 = \sum_{i=a+1}^{b} \left[ \pi\left( \frac{x}{p_i} \right) - i \right].
其中项数 ba=π(x)π(x1/3)=Θ(xlogx)b-a = \pi(\sqrt{x}) - \pi(x^{1/3}) = \Theta\left( \frac{\sqrt{x}}{\log x} \right)
需要计算每个 π(xpi)\pi(\frac{x}{p_i}), 其中 pix13p_i \ge x^{\frac{1}{3}}, 然后考虑预计算 π(m)\pi(m) 对于所有 mx23m \le x^{\frac{2}{3}} 可以使用埃氏筛完成,因此 P2P_2 的求和耗时为:
O(xlogx).O\left( \frac{\sqrt{x}}{\log x} \right).
接下来汇总:
注意到瓶颈过高,要优化(Lagarias–Miller–Odlyzko 1985) : 通过只保留 m=xtm = \lfloor \frac{x}{t} \rfloor 对于 tx13t \le x^{\frac{1}{3}}, 则计算 ϕ\phi 的时间为:
O(aS)=O(x13logxx13)=O(x23logx)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)
预计算一些小 mmϕ\phi 可以在 O(x23loglogx)O(x^{\frac{2}{3}} \log \log x) 内完成。
最后汇总得到:
O(x23logx+x23loglogx+xlogx)=O(x23logx).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(x23log2x)O\left( \frac{x^{\frac{2}{3}}}{\log^2 x} \right)
还是比 Min_25 快多了。

Part3: Powerful Number 筛

Powerful Number 筛,我们也称作 PN 筛,是最近几年才出现的一种非常厉害的筛法。
定义:正整数 nn 为 Powerful Number 当且仅当 nn 的所有质因子 pp 都有 p2np^2 \mid n
性质有下:
  • 11 是 PN, 这是一个约定。
  • PN 的数量,我们记 PN(N)\text{PN}(N) 为不超过 nn 的 PN 的数量,有 PN(N)ζ(32)ζ(3)N2.173N\text{PN}(N) \sim \frac{\zeta(\frac{3}{2})}{\zeta(3)} \sqrt{N} \approx 2.173 \sqrt{N} 如果你想写好一点,当然也可以说 PN(N)NlogN\text{PN}(N) \approx \frac{\sqrt{N}}{\log N} 量级。
那么这玩意对我筛 f(n)f(n) 前缀和有什么用啊,其核心思路在于我们需要先找一个辅助的函数 g(n)g(n) 满足:
  1. g(p)=f(p)g(p) = f(p)pp 为质数的时候成立。
  2. g(n)g(n) 的前缀和 G(n)=i=1ng(i)G(n) = \sum_{i=1}^n g(i) 便于计算,最好 gg 还是完全积性函数。
看到这个辅助函数,也许你就会想到卷积,确实,定义 hh 满足:
f=g×hf(n)=dng(d)h(nd)f = g \times h \quad \Leftrightarrow \quad f(n) = \sum_{d|n} g(d) h\left(\frac{n}{d}\right)
也就是狄利克雷卷积,其中 hh 必须为积性函数。
然后考虑从 hh 入手,先计算 h(pk)h(p^k)
  • n=pen = p^e 时,f(pe)=i=0eg(pi)h(pei)f(p^e) = \sum_{i=0}^e g(p^i) h(p^{e-i}), 这是卷积的关系。
  • 特别的。如果 e=1e = 1(素数),则 f(p)=g(p)h(1)+g(1)h(p)f(p) = g(p)h(1) + g(1)h(p), 带入条件有:g(p)=g(p)+h(p)h(p)=0g(p) = g(p) + h(p) \quad \Rightarrow \quad h(p) = 0
一条关键的结论:h(p)h(p)00 对于所有素数 pp 成立。质数部分就算完了。
然后考虑 hh00 值和 PN 的关系,由于 hh 为积性函数且 h(p)=0h(p) = 0pp 为质数下。那么对于包含一次质因子的 nn 都有 h(n)=0h(n) = 0, 这个非常好证明。
然后我们就要捣鼓前缀和了,前方高能!
f=g×hf = g \times h
f(n)=dng(d)h(nd)f(n) = \sum_{d|n} g(d) h\left(\frac{n}{d}\right)
带入求前缀和:
Sf(n)=i=1nf(i)=i=1ndig(d)h(id)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=idk = \frac{i}{d}i=dki = dkdid \mid i,交换求和:
Sf(n)=d=1ng(d)k=1ndh(k)S_f(n) = \sum_{d=1}^n g(d) \sum_{k=1}^{\lfloor \frac{n}{d} \rfloor} h(k)
由于 h(k)h(k) 仅在 kk 是 PN 时非 0,则我们可以尝试枚举所有可能的 kk,从原始的二层求和展开:
Sf(n)=i=1ndig(d)h(id)S_f(n) = \sum_{i=1}^n \sum_{d|i} g(d) h\left(\frac{i}{d}\right)
固定 kk 以后:
Sf(n)=k=1nh(k)d=1nkg(d)=k=1nh(k)G(nk)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)
根据我们上面的性质有:
Sf(n)=k=1k is PNnh(k)G(nk)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=pen = p^e 处:
f(pe)=i=0eg(pi)h(pei)f(p^e) = \sum_{i=0}^e g(p^i) h(p^{e-i})
分离 i=0i = 0 项:
f(pe)=g(1)h(pe)+i=1eg(pi)h(pei)f(p^e) = g(1)h(p^e) + \sum_{i=1}^e g(p^i) h(p^{e-i})
由于 g(1)=1g(1) = 1,得:
h(pe)=f(pe)i=1eg(pi)h(pei)h(p^e) = f(p^e) - \sum_{i=1}^e g(p^i) h(p^{e-i})
所以算法的步骤就是:
  • 找出辅助函数,满足上面要求。
  • 筛出所有 n\le \sqrt{n} 的质数,预处理一下 G(m)G(m)m=nkm = \lfloor \frac{n}{k} \rfloor
  • 最后计算每一个 hh,最后对 hh 进行求和就完了。

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

References:

评论

10 条评论,欢迎与作者交流。

正在加载评论...