数论 III(素数筛)

本文详细介绍了素数判断及其优化,包括优化后的判断算法,使用平方根界限减少检查次数。接着探讨了素数筛法,如朴素筛、埃拉托斯特尼筛和欧拉筛的工作原理及时间复杂度分析,揭示了筛法如何高效地找出给定区间内的素数。
摘要由CSDN通过智能技术生成

数论 III(素数筛)


>> 判断素数

\qquad 在大于 1 1 1 的质数中,除 1 1 1 和其自身外无其他因数的自然数叫做素数。从该定义下手,对于一个待判断的自然数 x x x,在 [ 2 , x − 1 ] [2,x-1] [2,x1] 的区间内寻找数 i i i 从而 i ∣ x i\mid x ix,若 i i i 存在,则 x x x 为合数;反之为素数。

bool PrimeJudge(int x) {
    if(x == 1) return 0;
    if(x == 2) return 1;
    for(int i = 2; i < x; i++) {
        if(x % i == 0) return 0;
    }
    return 1;
}

\qquad 对于上述代码,优化不难想出。判断素数时,只需要保证前 ⌊ n ⌋ \lfloor\sqrt{n}\rfloor n 个数不能整除 n n n ,那么 n n n 就是一个素数。证明很简单:设 p p p x x x 的因子之一,则 x p \dfrac{x}{p} px 也一定是 x x x 的因子之一。因而在 [ 2 , ⌊ n ⌋ ] [2,\lfloor\sqrt{n}\rfloor] [2,n ⌋] 区间内找到的因子 k k k,其对应的因子 x p \dfrac{x}{p} px 也唯一确定。

	...
    for(int i = 2; i * i <= x; i++) {
        if(x % i == 0) return 0;
    } 
	...

>> 素数筛

\qquad 筛法相较于素数的判断,在对象上有很大区别。素数的判断针对的是一个给定的数,而筛法针对的是一个给定的区间。素数的判断可以用于筛法:在区间 [ 1 , n ] [1,n] [1,n] 中枚举数 i i i 并进行素数判断,用数组记录。但时间复杂度实在是不可恭维,总的时间复杂度大概是 O ( n n ) \mathcal{O}(n\sqrt{n}) O(nn )

\qquad 筛法的思想就在于 ,大浪淘沙,一点一点将合数筛出来扔掉,剩下的就是素数。筛素数,就是找合数。


朴素筛

\qquad 设一合数 x x x,我们知道合数除 1 1 1 和其自身以外一定拥有其他因数,假设 x x x 拥有一个因数 p p p,则我们在找到 p p p 时,是否能将 x x x 筛出去?这便是最朴素的筛法。在之前的素数判断中,我们试着通过 分解 来判断素数,而现在我们的方法是 构造 。对于两个数 a , b a,b a,b,其乘积 c c c 一定是合数。这里我们已经确定了一个数 p p p,则只需枚举另一个数( p p p 扩大的倍数),从而找到合数。

void PrimSieve() {
    for(int i = 1; i <= MAXN; i++) isprime[i] = 1;
    isprime[1] = 0;
    for(int i = 2; i <= n; i++) { 
        for(int j = 2; i * j <= n; j++) { //i * 1 = i, 而 i 的合素性未知, 因而从 2 开始
            isprime[i * j] = 0;
        }
    }
}

Eratosthene 筛

\qquad 朴素筛中存在一点:合数也会执行筛的操作,这就造成了很多不必要的重复计算:如数字 12 12 12 12 = 2 × 6 = 4 × 3 12=2\times 6=4\times3 12=2×6=4×3,在枚举到 2 2 2 时, 12 12 12 会在 i = 2 , j = 6 i=2,j=6 i=2,j=6 时被筛掉,同样的在 i = 4 , j = 3 i=4,j=3 i=4,j=3 时会被筛掉。

\qquad 一般地,设有一合数 x x x 含有质因子 p p p,则一定存在 k ∈ Z k\in\mathbb{Z} kZ 使得 k p = x kp=x kp=x 。若存在一数 n n n x ∣ n x\mid n xn,则 k p ∣ n kp\mid n kpn 从而 p ∣ n p\mid n pn 。因此在筛的过程中, i i i 只用枚举素数便可包含所有情况。

void PrimeSieve() {
    for(int i = 1; i <= MAXN; i++) isprime[i] = 1;
    isprime[1] = 0;
    for(int i = 2; i * i <= n; i++) {
        if(!isprime[i]) continue;
        for(int j = 2 * i; j <= n; j += i) { //j 是乘之后的结果
            isprime[j] = 0;
        }
    }
}

\qquad 还有改进的空间。之前我们将 O ( n ) \mathcal{O}(n) O(n) 的算法优化为 O ( n ) \mathcal{O}(\sqrt{n}) O(n ) 的算法,其原理就是筛一个数时,被筛的那个数的因子一定可以唯一确定另一个因子,我们只由成对因子的较小的那一个因子筛掉这个数,而不由更大的那一个。即对于用来筛的素数 p p p ,应该从 p 2 p^2 p2 开始筛。如果从小于 p 2 p^2 p2 的数筛,那 p p p 就是 n n n 的一个较大的因子从而导致重复计算。只需要将第二层循环的起点改为 i 2 i^2 i2 即可。

void PrimeSieve() {
    for(int i = 1; i <= MAXN; i++) isprime[i] = 1;
    isprime[1] = 0;
    for(int i = 2; i <= n; i++) {
        if(!isprime[i]) continue;
        for(int j = i * i; j <= n; j += i) {
            isprime[j] = 0;
        }
    }
} 

Euler 筛

\qquad 还是以 12 12 12 为例。Eratosthene 筛的确将会避开 4 × 3 4\times 3 4×3 的情况,但 12 = 2 × 6 = 3 × 4 12=2\times 6=3\times 4 12=2×6=3×4,依旧会被重复计算。那么重复计算的根本在于什么?由于 12 12 12 等数的特殊性,它们拥有多于 1 1 1 对的因数(除开 1 1 1 和其自身),而每一次都会被每一组的最小值所筛掉,即重复计算的次数就是拥有的因数对数。举个例子: 30 = 2 × 15 = 3 × 10 = 5 × 6 30=2\times 15=3\times 10=5\times6 30=2×15=3×10=5×6,重复计算 3 3 3 次。我们期望的筛法是只被筛一遍,也就是用最小的那一个筛掉,比如说 12 12 12 只会被 2 2 2 筛一次, 3 3 3 就不会筛掉 12 12 12

void PrimeSieve() {
    for(int i = 1; i <= MAXN; i++) isprime[i] = 1; //step 1
	isprime[1] = 0;
	for(int i = 2; i <= n; i++) { //step 2
		if(isprime[i]) k++, prime[k] = i;
		for(int j = 1; j <= k && i * prime[j] <= n; j++) { //step 3
			isprime[i * prime[j]] = 0;
			if(i % prime[j] == 0) break; //step 4
		}
	}
}

\qquad 欧拉筛的实现比较特殊,不同于其他筛法,欧拉筛第一层枚举的是扩大的倍数 k k k,内层则枚举的是已经找到的素数。具体过程:

\qquad\quad 1) 预先建立一个 isprime 数组标记是否是素数,先标记为 1 1 1 ;再建立一个 prime 数组,初始为空,存储已经找到的素数。

\qquad\quad 2) 外层循环先枚举所有数(同时也是扩大的倍数),而不是直接用素数筛。

\qquad\quad 3) 内层循环取一个存储在 prime 数组中的素数,配合外层循环的数开始筛。如果目前 prime 数组的数已经被筛完了,或者筛出的数超过数据范围,就停止本轮内层循环。

\qquad\quad 4) 如果目前内层循环选定地素数是外层循环普通的数的一个因子,就跳出循环。

\qquad 值得注意的是外层循环的 i i i 既是待判断的具体的数也是扩大的倍数,因此无论是素数还是合数都会进入第二层循环,从而会有最后的判断。这个判断的意思是,如果外层循环的 i i i 是第 j j j 个素数的倍数,则跳出(这里的 i i i 就是另一个意思——枚举的具体的数)。这一步是为了防止出现重复计算的情况。假设我们要删除数 x x x,且 x x x 的最小质因数为 p 1 p_1 p1,令 x = q p 1   ( q ∈ Z ) x=qp_1\ (q\in\mathbb{Z}) x=qp1 (qZ),那么显然 q < a q < a q<a,则第一层循环最先达到 q q q 。那么 q q q 要筛掉它的倍数。因为 p 1 p_1 p1 x x x 的最小质因数,所以 q q q 的最小质因数一定不小于 p 1 p_1 p1,这保证了在 q q q 筛掉 x x x 之前并不会通过判断跳出循环。即使 q q q 的最小质因数等于 p 1 p_1 p1,也会先筛掉 x x x 后跳出循环。令 x x x 为全体合数,即可保证筛出所有合数。


>> 关于筛法的时间复杂度

目前只想出了暴力筛和 Eratosthene 筛的时间复杂度。


暴力筛

\qquad 对于每一个枚举到的数 i i i 都要进行 ⌊ n i ⌋ \left\lfloor\dfrac{n}{i}\right\rfloor in 次操作,因此总的操作次数为:
⌊ n 2 ⌋ + ⌊ n 3 ⌋ + ⌊ n 4 ⌋ + ⋯ + ⌊ n n ⌋ ≈ n ( 1 2 + 1 3 + 1 4 + ⋯ + 1 n ) = n ∑ i = 2 n 1 i \left\lfloor\dfrac{n}{2}\right\rfloor+\left\lfloor\dfrac{n}{3}\right\rfloor+\left\lfloor\dfrac{n}{4}\right\rfloor+\cdots+\left\lfloor\dfrac{n}{n}\right\rfloor\approx n\left(\dfrac{1}{2}+\dfrac{1}{3}+\dfrac{1}{4}+\cdots+\dfrac{1}{n}\right)=n\sum_{i=2}^{n}\dfrac{1}{i} 2n+3n+4n++nnn(21+31+41++n1)=ni=2ni1
\qquad 由于
∑ i = 1 n 1 i = ln ⁡ n + C \sum_{i=1}^n\dfrac{1}{i}=\ln n+C i=1ni1=lnn+C
\qquad 因而有
n ∑ i = 2 n 1 i = n ( ∑ i = 1 n 1 i − 1 ) = n ( ln ⁡ n + C − 1 ) ≈ n ln ⁡ n n\sum_{i=2}^n\dfrac{1}{i}=n\left(\sum_{i=1}^n\dfrac{1}{i}-1 \right)=n\left(\ln n + C-1\right)\approx n\ln n ni=2ni1=n(i=1ni11)=n(lnn+C1)nlnn
\qquad 则整体时间复杂度为 O ( n ln ⁡ n ) \mathcal{O}(n\ln n) O(nlnn)


Eratosthene 筛法

\qquad 对于外层循环枚举的每一个质数 i i i,都要将 i i i 的倍数标记为合数,而 i i i 的倍数在 [ 1 , n ] [1,n] [1,n] 中有 ⌊ n i ⌋ \left\lfloor\dfrac{n}{i}\right\rfloor in 个,因此总的循环次数为
⌊ n 2 ⌋ + ⌊ n 3 ⌋ + ⌊ n 5 ⌋ + ⋯ + ⌊ n p k ⌋ ≈ n ( 1 2 + 1 3 + 1 5 + ⋯ + 1 p k ) \left\lfloor\dfrac{n}{2}\right\rfloor+\left\lfloor\dfrac{n}{3}\right\rfloor+\left\lfloor\dfrac{n}{5}\right\rfloor+\cdots+\left\lfloor\dfrac{n}{p_k}\right\rfloor\approx n\left(\dfrac{1}{2}+\dfrac{1}{3}+\dfrac{1}{5}+\cdots+\dfrac{1}{p_k}\right) 2n+3n+5n++pknn(21+31+51++pk1)
\qquad 由于 k = π ( n ) k=\pi(n) k=π(n)
n ( 1 2 + 1 3 + 1 5 + ⋯ + 1 p k ) = n ∑ i = 1 π ( n ) 1 p i n\left(\dfrac{1}{2}+\dfrac{1}{3}+\dfrac{1}{5}+\cdots+\dfrac{1}{p_k} \right)=n\sum_{i=1}^{\pi(n)}\dfrac{1}{p_i} n(21+31+51++pk1)=ni=1π(n)pi1
\qquad 对于素数的倒数和,可以利用 mertens 第二定理(现学现卖)得
n ∑ i = 1 π ( n ) 1 p i = n ( log ⁡ log ⁡ n + B 1 + O ( 1 log ⁡ n ) ) n\sum_{i=1}^{\pi(n)}\dfrac{1}{p_i}=n\left(\log\log n+B_1+\mathcal{O}\left(\dfrac{1}{\log n} \right)\right) ni=1π(n)pi1=n(loglogn+B1+O(logn1))
\qquad 其中 B 1 B_1 B1 为 mertens 常数,则整体时间复杂度为
O ( n ( log ⁡ log ⁡ n + 1 log ⁡ n ) ) = O ( n log ⁡ log ⁡ n + n log ⁡ n ) ≈ O ( n log ⁡ log ⁡ n ) \mathcal{O}\left(n\left(\log\log n+\dfrac{1}{\log n}\right)\right)=\mathcal{O}\left(n\log\log n+\dfrac{n}{\log n} \right)\approx \mathcal{O}(n\log\log n) O(n(loglogn+logn1))=O(nloglogn+lognn)O(nloglogn)


Euler 筛

(未完,也比较短)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值