数论基础之筛法
筛素数的算法
¶朴素筛法
¶朴素的筛素数算法的流程是这样的:
没有被标记, 是素数;用 去标记所有的 被标记, 不是素数
因为每个数至少被所有它的素因子各自标记一次,而每个数的素因子个数是不超过
一个简单的优化技巧是:如果
线性筛
¶对于任意一个大于
先看怎么实现,再解释为什么是正确的且其复杂度是线性的。
程序实现
¶C++
sieve.prime.linear.cpp | 22 lines.12345678910111213141516171819202122// 一般来说,素数个数在 MAXN/10 ~ MAXN/15 左右typedef long long LL;bool isprime[MAXN];int prime[MAXN], tot;void sievePrimes() {memset(isprime, 1, sizeof isprime);tot = 0;for (int x = 2; x < MAXN; ++x) {if (isprime[x]) prime[tot++] = x;for (int i = 0; i < tot; ++i) {// 防止乘法溢出if ((LL) prime[i]*x >= MAXN) break;isprime[prime[i]*x] = false;// 保证每个数仅被其最小正素因子标记if (x % prime[i] == 0) break;}}}Typescript (也可以直接使用 @algorithm.ts/sieve-prime)
sieve-prime.linear.ts | 23 lines.1234567891011121314151617181920212223export function sievePrime(N: number): number[] {if (N <= 1) return []let tot = 0const primes: number[] = []const isNotPrime: Uint8Array = new Uint8Array(N)for (let x = 2; x < N; ++x) {// eslint-disable-next-line no-plusplusif (!isNotPrime[x]) primes[tot++] = xfor (let i = 0; i < tot; ++i) {if (primes[i] * x >= N) breakisNotPrime[primes[i] * x] = 1// Ensure that each number is only marked by its smallest positive factor.if (x % primes[i] === 0) break}}primes.length = totreturn primes}
正确性证明
¶如果 prime 栈中了。其次,不存在一个正素数
也就说,如果 prime[i]*x 是一个合数,所以这个筛法不会标记素数。据此,证明了该筛法的正确性。
线性复杂度证明
¶如果
,那么显然不用担心它被多个不同的素数标记过如果
若
;令 ,显然有 ,即上述程序第 19 行程序会得到执行。否则
,也就是说, 在遇见 时就会结束内层循环,不会遇见 ,所以保证了 至多仅被 筛一次。
综上,此筛法是
欧拉函数的筛法
¶欧拉函数
¶欧拉函数
定义函数:
那么,对于正整数
更一般地,将
下面利用容斥原理简单证明一下方程(2):
将
唯一分解,得: 。
令为比 小且被 整除的正整数集合,则有:
朴素筛法
¶方程(2) 虽然给出了欧拉函数的一个简单计算方法,但是,注意到质因数分解的复杂度是
123456789101112131415typedef long long LL;int phi[N];
void sievePhi() { memset(phi, 0, sizeof phi); phi[1] = 1; for (int i = 2; i < N; ++i) { if (!phi[i]) { for (int j = i; j < N; j += i) { if (!phi[j]) phi[j] = j; phi[j] = phi[i] / i * (i - 1); // 先除后乘防止溢出 } } }}
这个程序可以生成
线性筛
¶基于素数线性筛的思想,不妨设
当
时,由上述计算公式可知, ;当
时,由上述计算公式可知,
所以:
不难发现,该筛法与素数线性筛同阶,因此也是线性的。
C++
sieve.phi.linear.cpp | 24 lines.123456789101112131415161718192021222324typedef long long LL;int phi[N], prime[N], tot;void sievePhi() {memset(phi, 0, sizeof phi);phi[1] = 1;tot = 0;for (int i = 2; i < N; ++i) {if (!phi[i]) {phi[i] = i - 1;prime[tot++] = i;}for (int j = 0; j < tot; ++j) {LL ret = (LL) prime[j] * i;if (ret >= N) break;if (i % prime[j] == 0) {phi[ret] = phi[i] * prime[j]; // 对应 e 整除 X' 的情况break;}phi[ret] = phi[i] * (prime[j] - 1); // 对应 e 不整除 X' 的情况}}}Typescript (也可以直接使用 @algorithm.ts/sieve-totient)
sieve.phi.linear.ts | 35 lines.1234567891011121314151617181920212223242526272829303132333435export function sieveTotient(N: number,): [totient: Uint32Array, primes: number[]] {if (N < 1) return [new Uint32Array(0), []]if (N === 1) return [new Uint32Array(1), []]let tot = 0const totients: Uint32Array = new Uint32Array(N)const primes: number[] = []totients[1] = 1for (let x = 2; x < N; ++x) {if (totients[x] === 0) {// eslint-disable-next-line no-plusplusprimes[tot++] = xtotients[x] = x - 1}for (let i = 0; i < tot; ++i) {const target = primes[i] * xif (target >= N) break// Ensure that each number is only marked by its smallest positive factor.if (x % primes[i] === 0) {totients[target] = totients[x] * primes[i]break}totients[target] = totients[x] * (primes[i] - 1)}}primes.length = totreturn [totients, primes]}
练习
¶| Problem | Category | Solution / Code |
|---|---|---|
| UVa/GCD Extreme(II) | 欧拉函数 | - |
Related
¶