求素数(从判断素数到筛法)
判断素数
- 最简单的判断就是根据素数的定义:只有两个因子1和本身(1不是素数)。时间复杂度O(n)
bool is_prime(int x){
if(x == 1) return false;
rep(i , 2 , n-1){
if(x % i == 0){
return false;
}
}
return true;
}
- 我们知道因子都是成对出现的,所以在枚举时,可以只枚举2-\(\sqrt x\) .时间复杂度O(\(\sqrt n\))
bool is_prime(int x){
if(x == 1) return false;
rep(i , 2 , sqrt(x)){
if(x % i == 0){
return false;
}
}
return true;
}
- 数学上有个定理,只有形如6n-1,6n+1的自然数才可能是素数。简要说明一下:所有自然数都可以写成6n,6n+1,
6n+2,6n+3,6n+4,6n+5.可以简单的根据偶数一定不是素数排除6n,6n+2,6n+4.而6n+3可以写成3(2n+1)是大于3的倍数也一定素数。所以可以进一步优化。
bool is_prime(int x){
if(x == 2 || x == 3) return true;
if(x == 1 || (x%6!=1 &&x%6!=5)) return false;
rep(i , 2 , sqrt(x)){
if(x % i == 0){
return false;
}
}
return true;
}
埃氏筛法和欧拉筛法
- 埃氏筛法思路:用已经筛选出来的素数去过滤所有能被它整除的数,即满足p|x的自然数x。我们知道2是最小的素数,通过2筛去所有2的倍数。然后往后
3没有被筛,所以3是素数,通过3筛去所有3的倍数,以此类推。时间复杂度(\(nlog(log(n))\)),虽然欧拉筛时间复杂度降到O(n),但埃氏筛法时间复杂度已经非常优秀,足够使用,并且埃氏筛法实现简单
时间复杂度分析:小于\(\frac{n}{2}+\frac{n}{3}+...+\frac{n}{n} = nlogn\).时间复杂度为O(nlog(log(n))).
void Erasieve(int n){//筛选1-n间的素数
rep(i , 1 , n) is_prime[i] = true ;//初始都为素数
is_prime[1] = false; is_prime[2] = true ;
rep(i , 2 , n){
if(is_prime[i]){//判断是否为素数
//prime[++len] = i ;//加入素数表
for(int j = i*i ; j <= n ; j+=i){//通过该素数筛去素数倍数,j初始为i*i,而不是2*i,是因为我们知道2的倍数已经被筛了,算是一个小小的优化
is_prime[j] = 0;
}
}
}
}
- 欧拉筛法:因为埃氏筛法可能对一个数进行多次筛除,比如30,会被2和5各筛除1次。而欧拉筛法是对埃氏筛法的优化
使每一个数被筛除一次。要使每个数被筛除一次,则需要知道唯一分解定理。使每一个合数只被其最小质因数筛去。
具体思路:对于任意一个自然数n,假设n的最小质因数为m,那么我们用所有小于m的质数\(p_i\)乘上n可以得到一个合数。对于这个合数而言,
\(p_i\)一定是该合数最小质因数。因为n的最小质因数为m,\(p_i\)又为小于m的质数(可以看成是两部分相乘,一部分是大于m,这部分可能是质数也可能是合数,另一部分是\(p_i\)),
所以\(p_i\)一定是该合数的最小质因数。
void oulashai(int n){//筛选1-n素数
ME(is_prime , true);
is_prime[1] = false;
for(int i = 2 ; i <= n ; i++){
if(is_prime[i]){
prime[++len] = i ;//素数表
}
for(int j = 1 ; j <= len && prime[j] * i <= n ; j++){//素数表保证p为素数
is_prime[i*prime[j]] = false;//筛除n*p
if(i % prime[j] == 0) break;//保证p是小于等于m的最小质因数
}
}
}
埃氏筛法
应用一:区间无平方因子
求[n,m]区间无平方因子数的个数。整数p无平方因子,当且仅当不存在k>1,使得p是\(k^2\)的倍数.\((1<=n<=m<=10^{12} , m-n<=10^7)\)
思路:首先筛选出不超过\(\sqrt{m}\)素数表,然后对素数的平方进行筛选。核心思想是埃氏筛法。
int prime[maxn] , len;//素数表
bool is_prime[maxn];//标记素数
bool dprime[maxn];//标记具有平方因子数的数
void solve(){
int n , m , cnt = 0;
cin >> n >> m;//[n,m]区间
ME(is_prime , true);//初始化都为素数
is_prime[1] = 0 ;//1不是素数
for(int i = 2 ; i * i <= m ; i++){
if(is_prime[i]){//通过素数筛掉素数倍数,剩下的就是素数
prime[++len] = i ;
for(int j = i * i ; j <= m ; j += i){//2*i已被素数2筛去。所有从i*i开始
is_prime[j] = 0 ;
}
}
}
for(int i = 1 ; i <= len ; i++){
int p = prime[i]*prime[i];
for(int j = p ; j <= m ; j += p){//类似埃氏筛法,筛掉含素数平方因子的数
dprime[j] = 1 ;
}
}
rep(i , n , m){
if(!dprime[i]){//剩下的就是无平方因子的数
//cout << i << endl;
cnt++;
}
}
cout << cnt << endl;
}
应用二:区间筛法
给定整数l,r,求区间[l,r]的素数个数。\(l<=r<=10^{12} , r-l<=10^6\)
解法:筛出\(1-10^6\)质数 , 因为小于\(10^{12}\)的合数的最小质因子不超过\(10^6\)。可跳跃式的筛去较大的数。
for(int i = 2 ; i <= 1000000 ; i++){
if(!pr[i]){
for(int j = i * i ; j <= 1000000 ; j+=i){
pr[j] = 1 ;
if(j >= l && j <= r){
vis[j-l] = 1 ;
}
}
for(int j = max((ll)2 , (l+i-1)/i) ; j * i <= r ; j++){
vis[j*i-l] = 1 ;
}
}
}
if(l == 1){
vis[0] = 1 ;
}
vector<int>v;
for(int i = 0 ; i <= r-l ; i++){
if(!vis[i]){
v.push_back(i+l);
}
}
应用三:统计1-n每个数质因子个数
运用了埃氏筛法的思想.
void solve(int x){
pr[1] = 0 ;
for(int i = 2 ; i <= x ; i++){
if(!pr[i])
for(int j = i ; j <= x ; j *= i){
for(int k = j ; k <= x ; k += j){
pr[k]++;
}
}
}
}