【數論-1】質數/素數 篩法
阿新 • • 發佈:2021-10-27
質數/素數的篩法
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型別)
如果你使用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大佬)