1. 程式人生 > 其它 >【數論-1】質數/素數 篩法

【數論-1】質數/素數 篩法

質數/素數的篩法

1.質數/素數的樸素篩法

bool isprime[MAXN];
for(int i=2;i<=n;i++){
    isprime[i]=1;
    for(int j=2;j*j<=i;j++){
        if(i%j==0){
            isprime[i]=0;
            break;
        }
    }
}

關於此種篩法,對於一些簡單的入門題目是可以通過的(P1125

但不足之處是很明顯的,對於演算法競賽,“TLE”也是不能通過題目的一項重要原因。

那麼如何優化呢?列舉的次數過多時,可以減少列舉的次數。

優化:列舉到sqrt(n)即可

bool isprime[MAXN];
int prime[MAXN],cntprime=0;
for(int i=3;i<=n;i++){
    isprime[i]=1;
    for(int j=1;prime[j]*prime[j]<=i&&j<=cntprime;j++){
        if(i%prime[j]==0){
            isprime[i]=0;
            break;
        }
    }
    if(isprime[i]==1){
        prime[++cntprime]=i;
    }
}

2.質數/素數的埃氏篩+歐式篩

對於一個質數,它的倍數一定不是質數(可通過質數的定義來證明)

那麼我們可以篩出一個質數,然後對它的倍數進行標記:

bool isntprime[MAXN]={0};
for(int i=2;i<=n;i++){
    if(isntprime[i]==1) continue;
    for(int j=i+i;i<=n;j+=i){
        isntprime[j]=1;
    }
}

複雜度為O(nloglogn)

但其也不是完美的解決方案,比如12=2*6,12=3*4 這樣的數字可能會被篩多次(>=2)

這樣就造成了時間的浪費

我們對其改進:

bool isntprime[MAXN]={0};
for(int i=2;i<=n;i++){
    if(isntprime[i]==1) continue;
    for(int j=i*i;i<=n;j+=i){
        isntprime[j]=1;
    }
}

從p^2開始篩除即可,可將其優化,但其時間複雜度不改變

歐式篩(尤拉篩法)

尤拉(Euler)篩法,簡稱歐式篩,或因為其線性複雜度被稱呼為線性篩。由瑞士數學家尤拉提出

它在埃氏篩的基礎上,用一個方法,限定了每個數只被其最小質因數fcfc篩到一次,從而保證時間複雜度為O(n)O(n)

思想比較巧妙:

對於當前數字nn,假設它的最小質因數為fcfc

對於已經篩出的質數,存在表 prime 中

那麼,我們從質數表中,列舉最小質因數不大於fcfc的質數pp

我們就能保證:p×np×n的最小質因數一定為pp

那麼,事先沒被標記最小質因數的數字就一定是質數,且最小質因數為它本身

程式碼實現如下:

vector<int> Prime;
int fc[MAXN];
for(int i=2;i<=n;i++){
    if(fc[i]==0){
        fc[i]=i;
        Prime.push_back(i);
    }
    for(auto p : Prime)
        if(p>fc[i]||p*i>n) break;
        else fc[p*i]=p;
}//STL版本 

時間複雜度O(n)

3.對尤拉篩法空間的優化

對於歐式篩法,已經足夠優化速度,但對於空間的優化幾乎為0

如果你開一個prime[1e8+1],flag[1e8+1](int型別)

那麼不難想象空間會如何超出題目限制(P3383P3912

如果你使用Int型陣列 則難逃一死

so,可以開一個Bool 陣列來儲存每個數字的狀態(被篩與否)

然後處理

#include <iostream>
using namespace std;
const int maxn=1e8+1;
int n=0;int q=0;
int k=0;
int cnt=0;
bool flag[maxn]={0};
int primes[maxn]={0};
void getprimes(int x){
    for(int i=2;i<=x;i++){
        if(!flag[i]){
            primes[++cnt]=i;
        }
        for(int j=1;j<=cnt;j++){
            if(i*primes[j]>x){
                break;
            }
            flag[i*primes[j]]=1;
            if(!(i%primes[j])){
                break;
            }
        }
    }
}
int main(){
    std::ios::sync_with_stdio(0);
    cin>>n>>q;
    getprimes(n);
    for(int i=1;i<=q;i++){
        cin>>k;
        cout<<primes[k]<<endl;
    }
    return 0;
}
P3383
#include <iostream>
using namespace std;
const int max1=1e8+1;
bool flag[max1]={0};//儲存標記
int primes[max1];//存素數
int ans=0;
void getprimes(int x){
    for(int i=2;i<=x;i++){
        //從2開始判斷質數,1不是質數 
        if(!flag[i]){
            //預設0,取反之後為true->執行計數和儲存到陣列中
            
            primes[++ans]=i; 
            
        }
        for(int j=1;j<=ans;j++){//搜尋小於prime[i]的所有質數 
            if(i*primes[j]>x){
                break;
                //1.i*prime[j]不能大於所求範圍n;    
            }
            flag[i*primes[j]]=1;//標記flag[質數*質數]=合數
            if(!(i%primes[j])){
                break;//2.如果prime[j]是i的一個質因子,則退出,作為優化(後續的i*prime[j]會有更小的質因子存在,為了不除多次) 
            } 
        } 
    }
}
int main(){
    int n;
    cin>>n;
    getprimes(n);
    cout<<ans;
    return 0;
    
} 
P3912

還可以使用<bitset>的奇怪方法(Orz大佬)