素数筛选
素数
素数又称之为质数,若一个数只能被1和本身整除,没有其余的约数,这我们称这个数是素数,反之,这个数是合数;而且1既不是素数也不是合数。
素数的判定
朴素算法
bool isprime(int n){//单个数判断
for(int i = 2; i < n; i++){
if(n % i == 0) return false;
}
return true;
}
但是这种方法的时间复杂度很大,如果让你判断一个很大的数是否是素数,这种方法就会爆。那我们要怎样优化。
优化
首先,我们需要知道一个定理:任何一个正整数都可以用两个因数表示,例如\(12=2\times6\);
现在我们可以来证明一个定理:一个正整数n最多只有一个大于sqrt(n)的因数。
因为\(n=a\times b\)和\(n = \sqrt{n} \times \sqrt{n}\); 所以\(a \times b = \sqrt{n} \times \sqrt{n}\); 现在如果\(a\geq \sqrt{n}\)并且\(b\geq \sqrt{n}\),则\(a\times b\)一定大于\(n\);同理如果\(a \leq \sqrt{n}\)并且\(b \leq \sqrt{n}\),则\(a \times b\)一定小于\(n\),所以\(n = a \times b\)中,至少有一个数是大于\(\sqrt{n}\),一个小于\(\sqrt{n}\)。 所以在在枚举时,我们就不用再枚举到\(n-1\),枚举到\(\sqrt{n}\)就可以了
bool isprime(int n){
for(int i = 2; i <= sqrt(n); i++){
if(n % i == 0) return false;
}
return true;
}
6倍数判别法(快速判断)
原理:大于等于5的素数与6的倍数相邻。
证明:所有的自然数都可以使用集合\(A=\{6n,6n+1,6n+2,6n+3,6n+4,6n+5\}\)表示,\(n\geq 0\),显然,子集\(B = \{6n, 6n+2, 6n+3, 6n+4\}\)内的元素都不会是素数,所以只有\(6n+1\)和\(6n+5\)可能是素数,所以素数肯定可以使用\(6n+1\)和\(6n+5\)中的一个形式表示,即大于等于5的素数与6的倍数相邻。
判断:在判断一个数k是否是素数的时候,我们只需要判断这个数是否可以使用\(6n+1\)和\(6n+5\)表示,并且要判断\(t=6n+1\)和\(t=6n+5\)是否是k的因子即可。
#include <bits/stdc++.h>
using namespace std;
bool Isprime(int a){
if(a <= 1) return false;//如果这个数小于等于1,则这个数一定不是素数
if(a == 2 || a == 3) return true;
else if(a % 6 != 1 && a % 6 != 5) return false;//如果这个数不能使用6n+1和6n+5表示,则这个数不是素数
for(int i = 5; i <= sqrt(a); i+=6){
//若这个数可以使用6n+1和6n+5表示,但是它是前面素数的倍数,则这个数不是素数,例如55 = 6 * 9 + 1 = 11 * 5
if(a % i == 0 || a % (i+2) == 0) return false;
}
return true;
}
int main(){
for(int i = 1; i <= 10000; i++){
if(Isprime(i)) cout << i << " is prime" << endl;
}
return 0;
}
埃氏筛法
这个筛选方法是求自然数\(n\)以内的全部素数。
我们都知道任何一个数都可以被多个素数表示出来,于是我们在求素数时,可以把这个素数的倍数全部剔除。
例如:给出要筛选数值的范围\(n\),找出以内的素数。我们先用2去筛选,即留下2,把2的倍数全部剔除,然后再用下一个素数(也就是3),把3留下,把3的倍数全部剔除…………
const int MAXN = 1e6+10;//要筛选的范围
bool prime[MAXN];//用于记录这个数是不是素数
int arr[MAXN], t = 1;//arr存放素数
void Sieve_Prime(){
memset(prime,true,sizeof(prime));//把所有的数都先表示位素数
prime[0] = prime[1] = false;
for(int i = 2; i <= MAXN; i++){
if(prime[i]){//如果这个数是素数
for(int j = i + i; j <= MAXN; j += i){//这个数的倍数全部表示位非素数
prime[j] = false;
}
arr[t++] = i;//用另一个数组保留素数
}
}
}
欧拉筛选
在埃氏筛选中,存在有些数被重复多次筛选,例如:\(30\)这个数,\(30=2 \times 3 \times 5\),会被2的倍数筛选一次,3的倍数筛选一次,5的倍数筛选一次,这造成了筛选效率不是最优的。欧拉筛选法基于这种筛选进行了优化,在线性筛选中时间复杂度时O(N)。
欧拉筛选的核心思想:对于每一个合数,我们用其最小的质因子筛选它。
例如:(我们假定arr[]数组中存放这已经确定的素数)合数\(n = a\)(最小质因子)\(\times b\);如果\(n\%arr[j] ==0\);则\(n \times arr[j+1] = a\times b\times arr[j+1]\)可以被其后面的\(b\times arr[j+1]\)再乘以素数\(a\)筛选出来,所以当我们遇见\(n\%arr[j] ==0\)时要停止。
const int MAXN = 1e6+10;
bool prime[MAXN];
int arr[MAXN], t = 1;//存储素数
void Sieve_Prime(){
memset(prime,true,sizeof(prime));
prime[0] = prime[1] = false;
for(int i = 2; i <= MAXN; i++){
if(prime[i]) arr[t++] = i;//如果i是素数
for(int j = 1; j <= t && i * arr[j] <= MAXN; j++){
prime[i*arr[j]] = false;
if(i%arr[j] == 0) break;//停止筛选
}
}
}