转自:
点击打开链接
由于思想非常简单,故只给出实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
void eratosthenes_sieve(int n) { totPrimes = 0; memset(flag, 0, sizeof(flag)); int sqrtn = sqrt(n + 0.5); for (int i = 2; i <= sqrtn; i++) { if (!flag[i]) { primes[totPrimes++] = i; for (int j = i * i; j <= n; j += i) { flag[j] = true; } } } for (int i = sqrtn + 1; i <= n; i++) { if (!flag[i]) primes[++totPrimes] = i; } } |
时间复杂度 O(nloglogn) O(nloglogn)。(窝不会证明)
欧拉筛是一种线性算法,并且同时可以计算出每个数的 φ φ。
回顾经典的Eratosthenes筛法,它可能对同一个质数筛去多次。那么如果用某种方法使得每个合数只被筛去一次就变成是线性的了。
不妨规定每个合数只用其最小的一个质因数去筛,这便是欧拉筛了。
不妨先看代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
void euler_sieve(int n) { totPrimes = 0; memset(flag, 0, sizeof(flag)); for (int i = 2; i <= n; i++) { if (!flag[i]) primes[totPrimes++] = i; for (int j = 0; i * primes[j] <= n; j++) { flag[i*primes[j]] = true; if (i % primes[j] == 0) break; } } } |
请仔细体会i % primes[j] == 0
的含义。
时间复杂度 O(n) O(n)。
这个看似很简单,其实还是要注意一下细节的。搞清了证明其他的问题也就清楚了。
证明分两部分。首先证每个合数都会被筛到(正确性),其次证每个合数只会被筛到一次(复杂度)。
设有一合数 n=pk11...pkmm n=p1k1...pmkm( pi pi为质数)
则 n n一定会在 i=pk1−11...pkmm i=p1k1−1...pmkm 时被筛去(此时 primes[j]=p1 primes[j]=p1),因为对于小于 p1 p1的质数,一定不会被 i i 整除
与上面一样,还是设有一合数 n=pk11...pkmm n=p1k1...pmkm( pi pi 为质数)
倘若存在一个质因子 px(x≠1) px(x≠1) 也筛去了 n n,那么此时 i=pk11...pki−1x...pkmm i=p1k1...pxki−1...pmkm。
break
掉了,因为 p1∣i p1∣i。i % primes[j] == 0
为何不放在前面? 你可以去试试……实践出真知。
放前面的话,所有的“某个质因子的次数不为1”的合数便会被当成质数。至于为什么,请看证明。
j < totPrimes
为何不加?实践才是检验真理的唯一标准。
当然加了也没有问题。
其实这是极简单的。
主要基于以下事实:(很容易通过定义推出来,不妨自己试试)
1.n为质数时,phi(n) = n - 1
2.p为质数且p整除n时,phi(n*p) = p* phi(n)
3.p为质数且p不整除n时,phi(n*p) =(p - 1) * phi(n)
有没有发现简直就是为Euler筛法量身定做的!
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
void euler_sieve_with_phi(int n) { totPrimes = 0; phi[1] = 1; memset(flag, 0, sizeof(flag)); for (int i = 2; i <= n; i++) { if (!flag[i]) { primes[totPrimes++] = i; phi[i] = i - 1; } for (int j = 0; i * primes[j] <= n; j++) { flag[i*primes[j]] = true; if (i % primes[j]) phi[i*primes[j]] = phi[i] * (primes[j] - 1); else { phi[i*primes[j]] = phi[i] * primes[j]; break; } } } } |
你可能会觉得 loglogn loglogn 微不足道,那么你错了。
实测结果:(精确到微秒,编译时不打开优化开关)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
when n = 10000 eratosthenes_sieve(1229): 0(us) euler_sieve(1229): 0(us) when n = 100000 eratosthenes_sieve(9592): 999(us) euler_sieve(9592): 0(us) when n = 1000000 eratosthenes_sieve(78498): 13004(us) euler_sieve(78498): 7004(us) when n = 10000000 eratosthenes_sieve(664579): 185130(us) euler_sieve(664579): 79067(us) when n = 100000000 eratosthenes_sieve(5761455): 2363692(us) euler_sieve(5761455): 842592(us) when n = 1000000000 eratosthenes_sieve(50847534): 25535159(us) euler_sieve(50847534): 8987385(us) |
差距还是蛮大的呢。