通俗易懂的埃氏筛时间复杂度分析
前言
网上查阅了资料,发现对于埃氏筛时间复杂度的分析都很高深,大多运用了 Mertens Theorem https://en.wikipedia.org/wiki/Mertens'_theorems
然而本人水平太菜啦,根本看不懂。经过我一下午的摸索,自己 yy 出了一个较为通俗易懂的做法,如果您发现有纰漏,烦请在评论区中指出,谢谢!
埃氏筛是什么?
埃拉托斯特尼筛法,简称埃氏筛,是一种在 \(O(N\ln\ln N)\) 时间复杂度中筛出 \(1\sim N\) 之间所有素数的算法。
其算法过程就是,从 \(2\) 到 \(N\) 枚举每个数,如果当前数未被剔除,则其为素数,并将它的倍数全部剔除掉。循环结束之后,所有没被这种操作剔除的数都是素数。代码如下:
bool vis[N];
void Eratosthenes() {
memset(vis, false, sizeof(vis));
for (int i = 2; i <= N; i++) {
if (vis[i]) continue; // 合数就下一个
for (int j = i; j <= N / i; j++) vis[i * j] = true;
}
}
注意到这里有个小优化,每次倍数是从 \(i\) 开始枚举,因为小于 \(i\) 的倍数其实在之前的扫描中已经被标记过了。
这个算法时间复杂度为什么是 \(O(N\ln\ln N)\) 呢?正文开始:
埃氏筛时间复杂度分析
约定:为了方便表述,规定本文之后出现的所有 \(p\) 都是素数
我们的目标是求出:
上面的和式分为两部分,\(\sum\limits_{p\le \sqrt N} \dfrac{N}{p}\) 与 \(\sum\limits_{p\le \sqrt N}p\)
前置引理
我们之后所有的推导都基于素数定理,其表述如下:设 \(\pi (x)\) 表示不大于 \(x\) 的数中有多少个素数,则 \(\pi(x) \sim \dfrac{x}{\ln x}\)
则我们知道 \(x\) 为素数的概率为 \(\pi(x) - \pi(x-1) \approx \dfrac 1{\ln x}\)(这里实际上是期望,但由于只有一个数 \(x\),因此期望等于概率),这样,只要乘上概率,我们就能将对 \(p\) 的求和转化为连续的 \(x\) 的求和了!
有了这个引理,我们开始推导!
先算第一部分
接下来就要求这个含有 \(x\) 的和式,根据套路,我们可以使用积分近似。
这是个经典的换元法解决的积分式。不妨设 \(u = \ln x\),两边求导得 \(\mathrm{d} u = \dfrac 1x \mathrm{d} x\),发现这一项正好出现在积分式中,直接代入:
于是,将这个定积分代回到之前的式子中,第一部分的近似值为 \(O(N\ln\ln \sqrt N) =O(N\ln\ln N)\)
再算第二部分
经过我的尝试,我使用算两次方法来解决这个问题。
第一种计算方式与第一部分的相同:
第二种计算方式是将每个 \(p\) 拆分成 \(\sum_x [1\le x\le p]\) 的形式,计算贡献(类似阿贝尔变换):
两种计算方法联立,可以解得第二部分的近似值 \(O(\dfrac N {\ln N})\)
合并两部分
所以埃氏筛法的总复杂度为第一部分减去第二部分:
得证!