筛法
筛法,可能是数论算法中最古老、最有活力、最奇妙、最引人入胜的部分了。
埃拉托斯特尼筛法
Sift the Two's and Sift the Three's:
The Sieve of Eratosthenes.
When the multiples sublime,
The numbers that remain are Prime.—— Anonymous
埃拉托斯特尼筛,简称埃氏筛,它从 \(2\) 开始迭代地不断将每个素数地倍数标记为合数,剩下的自然都是素数。
由于这个过程很像一个筛子不断筛掉合数,留下质数,这种算法被称为“筛法”。
由于埃氏筛会重复标记合数,它的时间复杂度略高于线性,为 \(O(n\log\log n)\)。
略证
操作数组的次数为
\[O\left(\sum_{k=1}^{\pi(n)}\dfrac{n}{p_k}\right) \]只需证
\[\sum_{k=1}^{\pi(n)}\dfrac{1}{p_k}=O(\log\log n) \]由 \(\pi(n)=O\left(\dfrac{n}{\log n}\right)\),\(p_k=O(k\log k)\),
\[\begin{aligned} \sum_{k=1}^{\pi(n)}\dfrac{1}{p_k} &=O\left(\sum_{k=2}^{\pi(n)}\dfrac{1}{k\log k}\right)\\ &=O\left(\int_{2}^{\pi(n)}\dfrac{\text{d}x}{x\log x}\right)\\ &=O(\log\log\pi(n))=O(\log\log n)\\ &&\square \end{aligned} \]
我们可以每次标记合数时从 \(p^2\) 开始,这实际上不会改变渐进,但是实践上是重要的常数优化。
更进一步地,若使用 C++ STL 中地 bitset 作为标记数组,在使用 O2 优化的前提下,可以在 1s 筛出 \(2^{30}\) 以内的质数。
bitset<N> vis;
void sieve() {
vis[0] = vis[1] = true;
for (int i = 2; i * i < N; ++i)
if (!vis[i])
for (int j = i * i; j < N; j += i)
vis[j] = true;
}
上述代码预处理了 vis
数组,被标记为 true
意味着这一位不是质数。
可以 \(O(n)\) 再扫一遍 vis
数组,将 \(n\) 以内质数存储到另一个数组中。
欧拉筛
欧拉筛是埃氏筛的改进,可以做到 \(O(n)\) 筛出 \(n\) 以内的全体质数,但其常数略大,筛选质数的性能比不上埃氏筛,常被用于预处理积性函数。
具体来说,欧拉筛保证每个合数被它最小的质因数标记。
为了做到这一点,欧拉筛改变了标记质数时的枚举顺序。埃氏筛对于每个质数枚举倍数,而欧拉筛对每个倍数枚举质数:
bitset<N> vis;
vector<int> prime;
void sieve() {
vis[0] = vis[1] = true;
for (int i = 2; i < N; ++i) {
if (!vis[i]) prime.push_back(i);
for (int j: prime) {
vis[i * j] = true;
if (i % j == 0) break;
}
}
}
可以看到,函数体内,保证了 i * j
的最小质因子为 j
。
注意欧拉筛过程中可以得到每个数的最小质因子。
欧拉筛求积性函数
由于欧拉筛在标记每个合数时确保 j
是其最小质因子,非常方便在欧拉筛的过程中计算积性函数。
有些函数可以很方便的直接计算,如:
- 欧拉函数:\(\varphi(ij)=\varphi(i)\times j\)(\(i,j\) 含义与上述代码一致,且 \(j\mid i\),下同)
- 莫比乌斯函数:\(\mu(ij)=0\)
也有些函数需要额外维护数组,如:
- 约数个数函数,额外维护 \(n(i)\) 表示 \(i\) 中最小质因子的数量,那么:
\(n(ij)=n(i)+1,d(ij)=\dfrac{d(i)}{n(ij)}\cdot(n(ij)+1)\)
总之,若积性函数 \(f\) 可以 \(O(1)\) 计算 \(f(p^k)\)(至少可以 \(O(1)\) 递推出来),就可以在 \(O(n)\) 时间筛出 \(f\) 在 \(n\) 以内的点值。
这个方法有一个没大有用的扩展,可以做到 \(O\left(n+\dfrac{n\log k}{\log n}\right)\) 预处理,\(O(1)\) 查询 \(n\) 以内数的 \(k\) 次方,这里不展开说明了。
筛法意义的扩展
原因已难以考证,总之,在中国 OI 界,我们将一切关于“前 \(n\) 项”的数论求和问题都归为筛法。
有许多巧妙的和式处理技巧可以在亚线性时间内求数论函数的前缀和,这些方法被称之为“亚线性筛”。
记号说明
下文中:
- 字母 \(p\) 默认取值为质数集 \(\mathbf{P}\)
- 对于小写字母表示的数论函数,用大写字母表示其前缀和函数 i.e. \(F(n)=\sum_{i=1}^nf(i)\)
- 符号 \(\ast\) 表示狄利克雷卷积 i.e. \((f\ast g)(n)=\sum_{d\mid n}f(d)g\left(\dfrac{n}{d}\right)\)
- 下文的亚线性筛介绍中,以求 \(F(n)\) 为目标。
数论分块
尽管好像没人说这是“筛”,由于它也用于快速计算和式,也将其放在这里介绍。
假设可以 \(O(1)\) 计算或已经预处理了 \(f\) 的前缀和,数论分块可以在 \(O(\sqrt{n})\) 的复杂度内计算 \(\sum_{i=1}^{n}f(i)g\left(\left\lfloor\dfrac{n}{i}\right\rfloor\right)\)。
数论分块基于这样一个事实:\(\left\lfloor\dfrac{n}{i}\right\rfloor\) 的值在 \(i\) 接近时可能是相等的,如果我们把相等的部分提出来计算,可以减小 \(i\) 的枚举次数。
数论分块结论:值 \(\left\lfloor\dfrac{n}{i}\right\rfloor\) 所在块的右端点为 \(\left\lfloor\dfrac{n}{\left\lfloor\frac{n}{i}\right\rfloor}\right\rfloor\)。
略证
令 \(k=\left\lfloor\dfrac{n}{i}\right\rfloor\),则 \(k\le\dfrac{n}{i}\),于是
\[\left\lfloor\dfrac{n}{k}\right\rfloor\ge\left\lfloor\dfrac{n}{\frac{n}{i}}\right\rfloor=i \]故 \(i_{\max}=\left\lfloor\dfrac{n}{k}\right\rfloor=\left\lfloor\dfrac{n}{\left\lfloor\frac{n}{i}\right\rfloor}\right\rfloor\)
时间复杂度
引理 \(1\):
略证
- 对于 \(d\le\lfloor\sqrt{n}\rfloor\),\(\left\lfloor\dfrac{n}{d}\right\rfloor\) 有 \(\lfloor\sqrt{n}\rfloor\) 个不同取值;
- 对于 \(d\gt\lfloor\sqrt{n}\rfloor\),\(\left\lfloor\dfrac{n}{d}\right\rfloor\le\lfloor\sqrt{n}\rfloor\),也只有 \(\lfloor\sqrt{n}\rfloor\) 个不同取值。
由引理 \(1\),易知时间复杂度为 \(O(\sqrt{n})\)
更进一步地,如果是数论分块套数论分块,时间复杂度为 \(O(n^{3/4})\)。
略证
考虑数论分块的过程,
- 当 \(d\le \sqrt{n}\) 时,最多 \(\sqrt{n}\) 个块,套数论分块的复杂度是:
\[O\left(\sum_{i=1}^{\lfloor\sqrt{n}\rfloor}\sqrt{\dfrac{n}{i}}\right)=O\left(n\int_{1}^{\sqrt{n}}\dfrac{\text{d}x}{\sqrt{x}}\right)=O(n^{3/4}) \]
- 当 \(d>\sqrt{n}\) 时,同样最多 \(\sqrt{n}\) 个块,每块长 \(O(\sqrt{n})\),套数论分块的复杂度是:
\[O\left(\sqrt{n}\sqrt{\sqrt{n}}\right)=O(n^{3/4}) \]故总的时间复杂度也是 \(O(n^{3/4})\)。
杜教筛
由某位知名的杜姓 OI 选手引入中国 OI 界,据说是在 PE 论坛上看到的。
设法构造 \(F(n)\) 的递推式,引入另一数论函数 \(g\):
移项得
假设我们构造的函数 \(g\) 满足
- 可以快速计算 \(\sum_{i=1}^{n}(f\ast g)(n)\)
- 可以快速计算 \(g\) 的前缀和
那么,我们可以在短时间内计算 \(F(n)\)。
注意:
- 无论 \(f\) 是否是积性函数,只需构造适当的数论函数 \(g\),即可应用杜教筛。
- 可以用杜教筛求 \((f\ast g)\) 和 \(g\) 的前缀和,复杂度不变,但是常数增大。多套几层就比不上线性筛了。
时间复杂度
引理 \(2\):
略证
\[\begin{aligned} &\frac{a}{b}=\left\lfloor\frac{a}{b}\right\rfloor+r(0\leq r<1)\\ \implies &\left\lfloor\frac{a}{bc}\right\rfloor =\left\lfloor\frac{a}{b}\cdot\frac{1}{c}\right\rfloor =\left\lfloor \frac{1}{c}\left(\left\lfloor\frac{a}{b}\right\rfloor+r\right)\right\rfloor =\left\lfloor \frac{\left\lfloor\frac{a}{b}\right\rfloor}{c} +\frac{r}{c}\right\rfloor =\left\lfloor \frac{\left\lfloor\frac{a}{b}\right\rfloor}{c}\right\rfloor\\ &&\square \end{aligned} \]
引理 \(3\):令 \(R(n)=\left\{\left\lfloor\dfrac{n}{k}\right\rfloor:k=2,3,\cdots n\right\}\),则对于任意 \(m\in R(n)\),\(R(m)\subseteq R(n)\)。
由引理 \(2\),结论显然,不证。
设计算 \(f\ast g\) 和 \(g\) 的前缀和的复杂度均为 \(O(1)\),由引理 \(3\),每个 \(F(k)(k\in R(n))\) 均只计算 \(1\) 次。
由引理 \(2\),设计算 \(F(n)\) 的时间复杂度为 \(T(n)\)。
这个复杂度可以进一步优化,假设我们优化一部分 \(F(k)\),其中 \(k=1,2,\cdots,m\),\(m\ge\sqrt{n}\),设预处理复杂度为 \(T_0(m)\),此时 \(T(n)\) 变为:
若 \(T_0(m)=O(m)\),由均值不等式,取 \(m=\Theta(n^{2/3})\),此时杜教筛的复杂度取最小值 \(O(n^{2/3})\)。
以上证明来自 OI-wiki。
从以上证明,我们也看到:杜教筛天然适合搭配数论分块,因为两者所需前缀和的位置恰好吻合,总的复杂度仍为 \(O(n^{2/3})\),瓶颈是杜教筛。
Powerful Number 筛
我也不知道是谁引进的,但仍然怀疑来自 PE。
Powerful Number(以下简称 PN),可以用来求积性函数的前缀和,构造要求较高,但时间复杂度最低可达到 \(O(\sqrt{n})\)。
Powerful Number
首先,我们引入 PN。
定义:由唯一分解定理,记 \(n=\prod p_{i}^{\alpha_{i}}\),\(n\) 是 PN 当且仅当对所有 \(i\),满足 \(\alpha_i>1\)。特别地,令 \(1\) 也属于 \(\mathbf{PN}\)。
引理 \(4\):任何 PN 可以表为 \(a^2b^3\)。
结论显然,不证。
引理 \(5\):\(n\) 以内的 PN 有 \(O(\sqrt{n})\) 个。
证:枚举 \(a\),考虑满足条件的 \(b\) 的个数,用积分估算:
\[\int_{1}^{\sqrt{n}} \sqrt[3]{\frac{n}{x^2}} \mathrm{d}x = O\left(\sqrt{n}\right) \]
因此,预处理 \(\sqrt{n}\) 以内质数后,可以 DFS \(O(\sqrt{n})\) 枚举 \(n\) 以内 PN。
筛法过程
首先,构造积性函数 \(g\),满足 \(g(p)=f(p)\),最好能 \(O(1)\) 求和。
其次,构造积性函数 \(h\),满足 \(g\ast h=f\)。
对于质数 \(p\),\(f(p)=g(1)h(p)+g(p)h(1)=h(p)+g(p)\Rightarrow h(p)=0\)。由 \(h(n)\) 是积性函数,得 \(h(n)\) 仅在 PN 处取有效值。
由 \(f=g\ast h\),有
变换中利用了杜教筛的结论。
提前 \(O(\sqrt{n}\log n)\) 筛出需要的质数和 \(h(p^e)\) 的值,注意这个上界不紧,并且根据具体问题可能有优化空间。
接下来 \(O(\sqrt{n})\) 枚举 PN 求和即可。
若构造的 \(g(n)\) 需使用杜教筛求和,时间复杂度退化为 \(O(n^{2/3})\)。