數論學習筆記 (2):質數

godmoo發表於2024-04-29

\(\texttt{godmoo}\text{ }\text{の}\text{ }\texttt{數論學習筆記 之}\text{ }\boxed{質數}\)

定義

素數,又名質數,指的是在全體正整數中,僅能被 \(1\) 和它本身整除的數。
注意整除的概念是對整數而言的,但素數的研究僅針對與正整數。

性質

  • 質數的分佈:一般情況下,我們記 \(\pi(n)\)\([1,n]\) 中的質數的個數。黎曼給出了一個非常精確的計算公式,但是在 \(OI\) 中,我們暫且使用一下的公式:

\[\pi(n) \approx \frac{n}{In(n)} \]

  • 偶質數:\(2\) 是唯一的偶指數。

判定

這裡的判定指的是對於單個數的判定。

  • 試除法

根據質數的定義,我們只要逐一判斷是否能被 \([2,n-1]\) 中的整除即可。
又想到如果能被大於 \(\sqrt{n}\) 的數 \(k\) 整除,那麼必能被小於 \(\sqrt{n}\)\(\frac{n}{k}\) 整除,所以只需判斷 \([2,\sqrt{n}]\) 即可。
時間複雜度 \(O(\sqrt{n})\)

bool isprime(int n){
    for(int i=2;i*i<=n;i++){
        if(n%i==0) return false;
    }
    return true;
}

最佳化:可以判斷一下 \(n\)\(6\) 的值,顯然當 \(n\) 不為 \(2\)\(3\) 時,只有餘數為 \(1\)\(5\) 時才有可能是質數,此時不需判斷是否會被 \(2\)\(3\)\(4\) 整除,記得特判 \(2\)\(3\)

bool isprime(int n){
    if(n==2||n==3) return true;
    if(n%6!=1&&n%6!=5) return false;
    for(int i=5;i*i<=n;i++){
        if(n%i==0) return false;
    }
    return true;
}
  • \(\bm{Miller}\text{-}\bm{Rabin}\) 判斷法(待補)

篩法

篩法解決的是找出 \([2,n]\) 內所有的質數,如果對於每個數單個判斷,時間複雜度 \(O(n\sqrt{n})\),顯然不可接受。

  • 埃氏篩 \(Eratosthenes\)

我們可以從試除法的最佳化中找到一些靈感:我們從質因子的角度出發,將 \([1,n]\) 中它的倍數全部標記掉,那麼最後剩下的數就是質數。

舉個例子,當 \(n=10\) 時:

\(2\) 並沒有被標記,所以它是質數,標記它的倍數 \(4\)\(6\)\(8\)\(10\)
\(3\) 並沒有被標記,所以它是質數,標記它的倍數 \(6\)\(9\)
\(5\) 並沒有被標記,所以它是質數,標記它的倍數 \(10\)
\(7\) 並沒有被標記,所以它是質數,沒有剩下的數可以給它標記。
所以質數有 \(2\)\(3\)\(5\)\(7\)

這樣我們就找出了所有的質數。

那這個演算法的時間複雜度是多少呢?我們發現這個演算法的時間複雜度實際為\(O(\sum{\frac{n}{p}})\),也就是我們需要求出 \(O(\sum\frac{1}{p})\),這個東西是等於 \(O(loglogn)\) 的,於是複雜度為 \(O(nloglogn)\)證明我不會。

[程式碼應該很好寫]
  • 尤拉篩/線性篩

儘管埃氏篩的複雜度已經非常優秀了,但是遇到 \(luogu\) 的毒瘤題目還是招架不住,我們先來分析它的缺陷。

舉個例子,當 \(n=10\) 時:
\(2\) 並沒有被標記,所以它是質數,標記它的倍數 \(4\)\(6\)\(8\)\(10\)
\(3\) 並沒有被標記,所以它是質數,標記它的倍數 \(6\)\(9\)
\(5\) 並沒有被標記,所以它是質數,標記它的倍數 \(10\)
\(7\) 並沒有被標記,所以它是質數,沒有剩下的數可以給它標記。
所以質數有 \(2\)\(3\)\(5\)\(7\)

我們發現,\(6\) 被篩到了兩次,被 \(2\)\(3\) 篩到了。如果我們想追求線性複雜度,那麼就要使每個數剛好被篩到一次,這就是線性篩的思路。

容易想到,使用每個數的最小質因子將它篩掉,但是直接這麼做並不好維護,我們不如反過來,使用最大真因數篩掉他。我們可以發現:

\[最小質因子 \times 最大真因數 = 原數 \]

所以其實本質是一樣的。

void euler(){
	vis[1]=1;
	for(int i=2;i<=n;i++){
		if(!vis[i]) pr[++cnt]=i;
		// 對於每個數與質數表中的數相乘
		for(int j=1;j<=cnt&&i*pr[j]<=n;j++){ 
			vis[i*pr[j]]=1; // 可以乘出來的都是合數
			if(i%pr[j]==0) break; // 再往後枚,質數表中的就不是最小質因子了
		}
	}
}

相關文章