数论 III(素数筛)
>> 判断素数
\qquad 在大于 1 1 1 的质数中,除 1 1 1 和其自身外无其他因数的自然数叫做素数。从该定义下手,对于一个待判断的自然数 x x x,在 [ 2 , x − 1 ] [2,x-1] [2,x−1] 的区间内寻找数 i i i 从而 i ∣ x i\mid x i∣x,若 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} k∈Z 使得 k p = x kp=x kp=x 。若存在一数 n n n 且 x ∣ n x\mid n x∣n,则 k p ∣ n kp\mid n kp∣n 从而 p ∣ n p\mid n p∣n 。因此在筛的过程中, 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 (q∈Z),那么显然 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⌋+⋯+⌊nn⌋≈n(21+31+41+⋯+n1)=ni=2∑ni1
\qquad
由于
∑
i
=
1
n
1
i
=
ln
n
+
C
\sum_{i=1}^n\dfrac{1}{i}=\ln n+C
i=1∑ni1=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=2∑ni1=n(i=1∑ni1−1)=n(lnn+C−1)≈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⌋+⋯+⌊pkn⌋≈n(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 筛
(未完,也比较短)