線性篩是一個很基礎的算法,但是我一直沒學。直到一次考試,因為O(n√n)會超時,用了表篩,結果被卡了代碼長度,於是開始學習歐拉篩。
算法思路:
對於每一個數(無論質數合數)x,篩掉所有小於x最小質因子的質數乘以x的數。比如對於77,它分解質因數是7*11,那么篩掉所有小於7的質數*77,篩掉2*77、3*77、5*77。
好吧,是不是聽起來太簡單了。。。。沒事,重點在證明。
算法證明:
首先我們要明確證明思路。如果要證明它是對的,只要保證兩點:沒用重復篩、沒有漏篩
1、沒有重復篩。
我們假設一個合數分解成p1*p2*p3,並且保證p1<p2<p3。我們知道,篩掉這個合數的機會有:p1和p2*p3,p2和p1*p3,p3和p1*p2。但我們知道,我們選擇的那個質數必須小於那個合數的最小質因子。比如p2和p1*p3,因為p2>p1,所以這樣是篩不到的。唯一會篩的是第一種:p1和p2*p3。
2、沒有漏篩。
還是假設把這個合數分解質因數a*b*c,保證a<b<c然后我們設s=b*c,s肯定小於剛才那個合數,說明肯定對於它已經篩過,然后a肯定小於s,因為s=b*c,並且a是最小的因子。說明a*s也就是這個合數一定篩過。
證明沒看懂的直接看代碼吧。。挺好背的。
#include <iostream> #include <cstdio> #include <algorithm> #include <cstring> #include <cstdlib> #define in(a) a=read() #define REP(i,k,n) for(int i=k;i<=n;i++) using namespace std; inline int read(){ int x=0,f=1; char ch=getchar(); for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-1; for(;isdigit(ch);ch=getchar()) x=x*10+ch-'0'; return x*f; } int prime[1000010],book[1000010]; int n,ind; int main(){//計算1~n的素數 in(n); REP(i,2,n){ if(!book[i]) prime[++ind]=i;//如果沒有篩過,記錄素數 REP(j,1,ind){//其中記錄數組里的素數保證嚴格遞增 if(i*prime[j]>n) break;//保證小於n,要不然沒有意義 book[i*prime[j]]=1;//篩去這個合數 if(!i%prime[j]) break;//如果>=這個數的最小質因子,那就結束 } } REP(i,1,ind) printf("%d ",prime[i]);//輸出 }