從本節開始,咱們將進入數學的海洋(但願不會溺水),那就從質數開始咱們的數學知識之旅吧!算法
若一個正整數沒法被除了\(1\)和它自身之外的任何天然數整除,則稱該數爲質數(或素數),不然稱該數爲合數。數組
--《算法競賽進階指南》ide
在整個天然數集合中,質數的分佈比較稀鬆,大約每\(lnN\)個數中有一個質數,即對於正整數\(N\),不超過其的質數大約有\(\frac{N}{lnN}\)個。優化
試除法:若一個正整數\(N\)爲合數,則存在一個能整除\(N\)的正整數數\(T\),知足\(2 \le T \le \sqrt{N}\)。spa
理解:若存在一個正整數\(M\)使得\(M|N\)(\(N\)被\(M\)整除)且\(M \in (\sqrt{N},N)\),則正整數\(\frac{N}{M}\)也能夠整除\(N\)且\(\frac{N}{M} \in [2,\sqrt{N}]\)code
因此判斷一個數是否爲質數,咱們只須要掃描$2 - \sqrt{N} \(之間的全部整數,依次判斷其是否整除\)N\(,都不整除則\)N\(爲質數,不然爲合數。其時間複雜度爲\)O(\sqrt{N})$。blog
代碼以下:ci
il bool id_prime(int n) { //質數返回1,合數返回0 if(n<2) return 0; //特判0和1,它們是合數 for(re int i=2;i*i<=n;++i) //依次掃描 if(n%i==0) return 0;//能被整除,就是合數 return 1;//是質數 }
固然,咱們也能夠對其進行一些常數優化:get
il bool is_prime(int n) { if(n<2) return 0; if(n==2||n==3) return 1; if(n%6!=1&&n%6!=5) return 0; for(re int i=5;i*i<=n;i+=6) if(n%i==0||n%(i+2)==0) return 0; return 1; }
此方法基於一個原理:博客
除\(2\)、\(3\)外的全部質數,除以\(6\)的餘數必定爲\(1\)或\(5\)。
理解:一個數除以\(6\),其他數爲\(0\)、\(1\)、\(2\)、\(3\)、\(4\)、\(5\)
對於餘數爲\(0\)、\(2\)、\(4\)的數,其必定是\(2\)的倍數。
對於餘數爲\(0\)、\(3\)的數,其必定是\(3\)的倍數。
因此咱們只要判斷\(N\)是否整除\(6n-1\)或\(6n+1\)便可(其中\(n \ge 1\)且\(6n+1 \le \sqrt{N}\))
這種方法常數極小,大約爲\(\frac{\sqrt{N}}{3}\)。
題意簡述:給定一個正整數\(N\),求\(1-N\)之間全部質數。
首先,咱們想到的最樸素的想法就是枚舉\(1-N\),依次判斷每一個數是不是質數。顯然,這樣作太慢了,咱們就能夠引進一些算法來優化它。
就是\(OIer\)們口中常說的埃氏篩,它的基本思路是:任何一個整數\(x\),其倍數\(2x\)、\(3x\)......\(\left\lfloor\dfrac{N}{x}\right\rfloor \times x\)都是合數。
實際上,小於\(x^2\)的\(x\)的倍數在掃描更小的數時已經被標記過了。
理解:\(2x\)在標記\(2\)時標記了,\(3x\)在標記\(3\)時標記了.....前\(2\)到\(x-1\) \(\times x\)都在標記比\(x\)小的質數時被標記過了。
因此咱們只須要從\(x^2\)標記到\(\left\lfloor\dfrac{N}{x}\right\rfloor \times x\)便可,其時間複雜度爲\(O(\sum_{p \le N}{\frac{N}{p}}) = O(N log log N)\),其中\(p\)表示小於\(N\)的質數。
代碼以下:
bool vis[N];//標記數組 int pri[N],m;//pri[i]記錄質數,m表示質數個數 il void primes(int n) { for(re int i=2;i<=n;++i) { if(vis[i]) continue;//標記過,直接下一層循環 pri[++m]=i;//統計質數 for(re int j=i;j<=n/i;++j) vis[i*j]=1;//標記i的倍數 } }
咱們都知道,埃氏篩最大的瓶頸就是會重複標記質數(即便從\(x^2\)開始標記也會),那咱們可不可讓每個數只被標記一次呢?答案是確定的,歐拉篩就給咱們提供了這樣的思路:其經過從小到大累計質因子的方法,令\(vis[i]\)數組只記錄\(i\)的最小質因子,達到線性篩質數。
其過程以下:
掃描\(2\)到\(N\)中的每一個數,若vis[i]==0
,則令vis[i]=i
並把它保存進質數數組。
掃描質數數組中不大於\(vis[i]\)的全部質數(設爲\(p\)),標記vis[i*p]=p
。(由於\(p \le vis[i] \le i\),因此\(p\)是合數\(i \times p\)的最小質因子)
由於每個合數均可以被表示成\(i \times p\)的形式,且其只會被其最小質數\(p\)篩一次,因此其時間複雜度爲\(O(N)\)。
代碼以下
int vis[N];//標記數組 int pri[N],m;//pri[i]記錄質數,m表示質數個數 il void primes(int n) { for(re int i=2;i<=n;++i) { if(!vis[i]) {vis[i]=i;pri[++m]=i;}//沒被標記過,是質數 for(re int j=1;j<=m;++j) {//枚舉質數數組 if(pri[j]>vis[i]||pri[j]>n/i) break; //pri[j]不是i*pri[j]的最小質數或i*pri[j]>n就不用繼續標記 vis[i*pri[j]]=pri[j]; //標記i*pri[j]的最小質因子pri[j] } } }
算數基本定理:任何一個大於\(1\)的正整數都能惟一分解爲有限個質數的乘積。可寫做:
其中\(c_i\)都是正整數,\(p_i\)都是質數,且知足\(p_1 < p_2 < ... < p_m\)。
個人理解:
首先,根據質數的定義,質數只能被一和它自己整除,因此質數符合這個定理
又由於合數能被除了一和它自己的數整除(設這個數爲\(p\)),若是\(p\)是質數,那就符合這個定理, 若是\(p\)是合數,就繼續往下分。
咱們能夠掃描\(2 \sim \left\lfloor{\sqrt{N}}\right\rfloor\)中的每一個數\(d\),若\(d|N\),則從\(N\)中除去全部的因子\(d\),同時累計除去的\(d\)的個數。
由於合數確定在以前就被其質因數除去,因此最後得出的必定是質因數分解的結果,時間複雜度爲\(O(\sqrt{N})\)。
代碼以下:
int pri[N],c[N],m;//p[i]記錄質因數,c[i]記錄第i個質因數的個數 il void divide(int n) { for(re int i=2;i*i<=n;++i) { if(n%i==0) {//找到能夠整除的數 pri[++m]=i,c[m]=0; while(n%i==0) n/=i,++c[m];//統計該質因子的個數 } } if(n>1) pri[++m]=n,c[m]=1;//若是最後n也是質數,把n加入答案數組 }
例題:
本題是一道有意思的素數判斷題(幸虧出題人很良心),根據伯特蘭-切比雪夫定理:若整數\(n > 3\),則至少存在一個質數\(p\),符合\(n\) \(<\) \(p\) \(<\) \(2 \times n\) \(-\) \(2\),咱們就能夠對\(k+1\)進行分類討論:
若\(k+1\)是質數:則第一天它能夠傳遍全部不是它的倍數的數(質數與全部不是它倍數的數互質),次日就能夠由不是它的倍數的數\(-1\)傳給它(相鄰的兩個數互質)。(顯然,若是\(2 \sim n+1\)都沒有該質數的倍數,一天就傳完了)
若\(k+1\)不是質數:則第一天它能夠傳遞到一個屬於\([\left\lfloor\dfrac{n}{2}\right\rfloor,n]\)中的一個素數,再由該素數傳遞到全部數。
本題中\(l\)和\(r\)特別大,但\(r-l \le 1e6\),因此咱們能夠預處理\(2 \sim \sqrt{r_{max}}\)中的全部質數,在對於每一組\(l\)和\(r\),將\(l \sim r\)中間的全部質數的倍數標記,在剩下的未標記的數中找相鄰的最大值和最小值便可。
注意點:
當\(l<2\)是,把\(l\)設爲\(2\)。
標記區間的合數時,可讓該區間都減去\(l\),只用開一個\(1e6\)的\(bool\)數組便可。
思路:由於\(1 \sim n\)中的數的質因子最大不超過\(n\),因此能夠先預處理出\(1 \sim n\)中的全部質數,設質數爲\(p\),則\(N!\)中質因子\(p\)的個數爲:
對於每一個\(p\),咱們只須要\(O(logN)\)的時間計算上述和式,因此整個算法的時間複雜度爲\(O(NlogN)\)。
《算法競賽進階指南》