素数筛
筛选出从2~n的所有素数
埃氏筛
对2~n中的每个数k,筛去k在2~n中的每一个倍数,最后剩下的就是2~n的所有素数,因为相当于每一个数k都会被2~k - 1的所有数筛一遍,一旦k存在2~k - 1的约数,那么他必定会被筛掉,反之k一定不会被筛掉。
注意:k是素数 <=> k不存在2~k - 1的约数,所以在埃氏筛中一定不可以出现一个数把自己筛掉的情况(即约数是不能包括数字k本身的)
朴素版
#include<iostream>
using namespace std;
const int N = 1000010;
int st[N], primes[N];
int cnt;
int n;
void get_primes(int n){
for(int i = 2; i <= n; i ++){
if(!st[i]) primes[cnt ++] = i;
for(int j = i + i; j <= n; j += i) st[j] = 1;
}
}
int main(){
cin >> n;
get_primes(n);
cout << cnt;
}
复杂度:
\(\frac{n}{2} + \frac{n}{3} +...+\frac{n}{n}\)
\(=n(\frac{1}{2} + \frac{1}{3} +...+\frac{1}{n})\)
\(=n(ln(n + 1) -1 + \delta)\)
\(\approx nln(n) \lt nlog_2n\)
所以复杂度计为\(O(nlogn)\)
优化版
和朴素版的唯一区别:朴素版用所有的数筛,优化版用素数来筛
k为素数 <=> k不存在2~k - 1中的质因数,即没有必要用2~ k - 1中的所有数来筛k,只需要用2~k - 1中的质数来筛k。
因为如果一个数k有约数p,那么k会被p筛掉,由于k的倍数也一定是p的倍数,所以k的所有倍数也会被p筛掉,所以没有必要用k再筛一遍。
注意:优化版依然存在重复筛的情况,因为一个数可能不止一个质因子,所以还是会重复筛。
#include<iostream>
using namespace std;
const int N = 1000010;
int st[N], primes[N];
int cnt;
int n;
void get_primes(int n){
for(int i = 2; i <= n; i ++){
if(!st[i]){
primes[cnt ++] = i;
for(int j = i + i; j <= n; j += i) st[j] = 1;
}
}
}
int main(){
cin >> n;
get_primes(n);
cout << cnt;
}
复杂度:
真实复杂度:\(O(nloglogn)\)
参照朴素版,优化版相当于只计算了\(\frac{n}{2} + \frac{n}{3} +...+\frac{n}{n}\)分母为素数的部分,又由质数定理(不超过n的素数个数为n/ln(n)),可得近似的时间复杂度为\(O(n) ,(nln(n)/ln(n) = n)\),这是错的,但是和真实复杂度很接近。
线性筛
保证了不重复筛的一种筛法。一个数用他的最小质因子筛,因为每个数的最小质因子唯一,所以每一个数都只会被筛一次。
比如:12 = 2 * 2 * 3,他只会被2筛掉一次,而不会再被3重复筛。
如果12被3筛,意味着一定是用4*3来筛的,但是4根本不会用3来筛,因为4只会用小于等于他的最小质因子的数和4相乘来筛别的数。
void get_primes(int n){
for(int i = 2; i <= n; i ++){
if(!st[i]) primes[cnt ++] = i;
//和埃氏筛的区别在这里
for(int j = 0; primes[j] <= n / i; j ++){ //primes[j] <= n / i;是为了不越界,只筛n以内的合数
st[primes[j] * i] = 1;
if(i % primes[j] == 0) break;
}
}
}
首先j从小到大枚举素数,那么枚举到的质因子一定是最小的。
- i % primes[j] != 0时,primes[j]一定小于i的所有质因子,那么primes[j]一定是primes[j] * i的最小质因子,把primes[j] * i给筛掉是对的。
- i % primes[j] == 0时,primes[j]是i的最小质因子,同样也是primes[j] * i的最小质因子,所以把primes[j] * i给筛掉也是对的。
由1和2可知,算法确实是在用最小质因子来筛掉合数。
现在证明一个合数一定会被筛并且只会被筛一次
1)一个合数一定会被筛
设x为2~n的一个合数,那么x一定存在一个最小质因子p,即x会被x / p筛掉,而在i枚举到x之前,一定会先枚举到x / p,并且由于x是合数且最小质因子是p,那么有x / p > p, 所以i枚举到x / p时,质数表里一定含有p,所以x一定会被筛掉,又因为i的枚举范围为2~n,并且2~n的所有合数除以它的最小质因子的范围仍在2~n,所以所有2~n的合数都会被筛掉。
2)一个合数只会被它的最小质因子筛(已证),且x不会被同一个最小质因子筛多次(x含有多个相同的最小质因子)
因为合数x是被其最小质因子p筛,即在i = x / p时,将x筛掉,由于x / p是唯一值,所以只需要证明在i = x / p时,只会把x筛一次即可。
-
当i = x / p的最小质因子不是p: x只被筛一次
-
当i = x / p的最小质因子是p:x只被筛一次
另外质数是不会被筛掉的,因为质数t的最小质因子为t,而i >= 2, 所以i * t绝对不可能为t,所以t不会被筛掉。
还有一个注意点:for(int j = 0; primes[j] <= n / i; j ++)
为什么不写成for(int j = 0; j < cnt && primes[j] < n / i; j ++)
,若i为合数则i一定存在质因子,若i为质数,那i一定位于primes数组的最后一个位置,这两种情况都会被if(i % primes[j] == 0) break;
停下来,所以j一定会在达到cnt之前停下来。
总之,线性筛要想清楚:
- 为什么合数一定会被筛掉
- 为什么质数一定不会被筛掉
- 为什么所有合数都只会被筛一次
复杂度:\(O(n)\)
给定一个正整数n,请你求出1~n中质数的个数。
输入格式
共一行,包含整数n。
输出格式
共一行,包含一个整数,表示1~n中质数的个数。
数据范围
\(1≤n≤10^6\)
#include<iostream>
using namespace std;
const int N = 1000010;
int st[N], primes[N];
int cnt;
int n;
void get_primes(int n){
for(int i = 2; i <= n; i ++){
if(!st[i]) primes[cnt ++] = i;
for(int j = 0; primes[j] <= n / i; j ++){
st[primes[j] * i] = 1;
if(i % primes[j] == 0) break;
}
}
}
int main(){
cin >> n;
get_primes(n);
cout << cnt;
}
埃氏筛和线性筛耗时在\(10^6\)时基本相同,在\(10^7\)时埃氏筛时间翻一倍。