vector<int> prime;bool is_prime[N];void Eratosthenes(int n) { is_prime[0] = is_prime[1] = false; for (int i = 2; i <= n; ++i) is_prime[i] = true; for (int i = 2; i <= n; ++i) { if (is_prime[i]) { prime.push_back(i); if ((long long)i * i > n) continue; for (int j = i * i; j <= n; j += i) // 因为从 2 到 i - 1 的倍数我们之前筛过了,这里直接从 i // 的倍数开始,提高了运行速度 is_prime[j] = false; // 是 i 的倍数的均不是素数 } }}
Python
prime = []is_prime = [False] * Ndef Eratosthenes(n): is_prime[0] = is_prime[1] = False for i in range(2, n + 1): is_prime[i] = True for i in range(2, n + 1): if is_prime[i]: prime.append(i) if i * i > n: continue for j in range(i * i, n + 1, i): is_prime[j] = False
vector<int> prime;bool is_prime[N];void Eratosthenes(int n) { is_prime[0] = is_prime[1] = false; for (int i = 2; i <= n; ++i) is_prime[i] = true; // i * i <= n 说明 i <= sqrt(n) for (int i = 2; i * i <= n; ++i) { if (is_prime[i]) for (int j = i * i; j <= n; j += i) is_prime[j] = false; } for (int i = 2; i <= n; ++i) if (is_prime[i]) prime.push_back(i);}
Python
prime = []is_prime = [False] * Ndef Eratosthenes(n): is_prime[0] = is_prime[1] = False for i in range(2, n + 1): is_prime[i] = True # 让 i 循环到 <= sqrt(n) for i in range(2, isqrt(n) + 1): # `isqrt` 是 Python 3.8 新增的函数 if is_prime[i]: for j in range(i * i, n + 1, i): is_prime[j] = False for i in range(2, n + 1): if is_prime[i]: prime.append(i)
值得注意的是,我们在处理第一个数字时需要稍微修改一下策略:首先,应保留 [1,n] 中的所有的质数;第二,数字 0 和 1 应该标记为非素数。在处理最后一个块时,不应该忘记最后一个数字 n 并不一定位于块的末尾。
以下实现使用块筛选来计算小于等于 n 的质数数量。
实现
int count_primes(int n) { constexpr static int S = 10000; vector<int> primes; int nsqrt = sqrt(n); vector<char> is_prime(nsqrt + 1, true); for (int i = 2; i <= nsqrt; i++) { if (is_prime[i]) { primes.push_back(i); for (int j = i * i; j <= nsqrt; j += i) is_prime[j] = false; } } int result = 0; vector<char> block(S); for (int k = 0; k * S <= n; k++) { fill(block.begin(), block.end(), true); int start = k * S; for (int p : primes) { int start_idx = (start + p - 1) / p; int j = max(start_idx, p) * p - start; for (; j < S; j += p) block[j] = false; } if (k == 0) block[0] = block[1] = false; for (int i = 0; i < S && start + i <= n; i++) { if (block[i]) result++; } } return result;}
分块筛法的渐近时间复杂度与埃氏筛法是一样的(除非块非常小),但是所需的内存将缩小为 O(n+S),并且有更好的缓存结果。
另一方面,对于每一对块和区间 [1,n] 中的素数都要进行除法,而对于较小的块来说,这种情况要糟糕得多。
因此,在选择常数 S 时要保持平衡。
块大小 S 取 104 到 105 之间,可以获得最佳的速度。
线性筛法
埃氏筛法仍有优化空间,它会将一个合数重复多次标记。有没有什么办法省掉无意义的步骤呢?答案是肯定的。
如果能让每个合数都只被标记一次,那么时间复杂度就可以降到 O(n) 了。
实现
[list2tab]
C++
vector<int> pri;bool not_prime[N];void pre(int n) { for (int i = 2; i <= n; ++i) { if (!not_prime[i]) { pri.push_back(i); } for (int pri_j : pri) { if (i * pri_j > n) break; not_prime[i * pri_j] = true; if (i % pri_j == 0) { // i % pri_j == 0 // 换言之,i 之前被 pri_j 筛过了 // 由于 pri 里面质数是从小到大的,所以 i 乘上其他的质数的结果一定会被 // pri_j 的倍数筛掉,就不需要在这里先筛一次,所以这里直接 break // 掉就好了 break; } } }}
Python
pri = []not_prime = [False] * Ndef pre(n): for i in range(2, n + 1): if not not_prime[i]: pri.append(i) for pri_j in pri: if i * pri_j > n: break not_prime[i * pri_j] = True if i % pri_j == 0: """ i % pri_j == 0 换言之,i 之前被 pri_j 筛过了 由于 pri 里面质数是从小到大的,所以 i 乘上其他的质数的结果一定会被 pri_j 的倍数筛掉,就不需要在这里先筛一次,所以这里直接 break 掉就好了 """ break
上面的这种 线性筛法 也称为 Euler 筛法(欧拉筛法)。
Note
注意到筛法求素数的同时也得到了每个数的最小质因子。
筛法求欧拉函数
注意到在线性筛中,每一个合数都是被最小的质因子筛掉。比如设 p1 是 n 的最小质因子,n′=p1n,那么线性筛的过程中 n 通过 n′×p1 筛掉。
vector<int> pri;bool not_prime[N];int phi[N];void pre(int n) { phi[1] = 1; for (int i = 2; i <= n; i++) { if (!not_prime[i]) { pri.push_back(i); phi[i] = i - 1; } for (int pri_j : pri) { if (i * pri_j > n) break; not_prime[i * pri_j] = true; if (i % pri_j == 0) { phi[i * pri_j] = phi[i] * pri_j; break; } phi[i * pri_j] = phi[i] * phi[pri_j]; } }}
Python
pri = []not_prime = [False] * Nphi = [0] * Ndef pre(n): phi[1] = 1 for i in range(2, n + 1): if not not_prime[i]: pri.append(i) phi[i] = i - 1 for pri_j in pri: if i * pri_j > n: break not_prime[i * pri_j] = True if i % pri_j == 0: phi[i * pri_j] = phi[i] * pri_j break phi[i * pri_j] = phi[i] * phi[pri_j]
筛法求莫比乌斯函数
定义
根据莫比乌斯函数的定义,设 n 是一个合数,p1 是 n 的最小质因子,n′=p1n,有:
μ(n)=⎩⎨⎧0−μ(n′)n′modp1=0otherwise
若 n 是质数,有 μ(n)=−1。
实现
[list2tab]
C++
vector<int> pri;bool not_prime[N];int mu[N];void pre(int n) { mu[1] = 1; for (int i = 2; i <= n; ++i) { if (!not_prime[i]) { mu[i] = -1; pri.push_back(i); } for (int pri_j : pri) { if (i * pri_j > n) break; not_prime[i * pri_j] = true; if (i % pri_j == 0) { mu[i * pri_j] = 0; break; } mu[i * pri_j] = -mu[i]; } }}
Python
pri = []not_prime = [False] * Nmu = [0] * Ndef pre(n): mu[1] = 1 for i in range(2, n + 1): if not not_prime[i]: pri.append(i) mu[i] = -1 for pri_j in pri: if i * pri_j > n: break not_prime[i * pri_j] = True if i % pri_j == 0: mu[i * pri_j] = 0 break mu[i * pri_j] = -mu[i]