數論是屬於純數學的一個領域,專注於研究「整數」的性質。
因為很常為基礎數學訓練的一個項目,所以也被稱為「The Queen of Mathematics」
在資訊領域中最常應用到的是在密碼學
競賽中數論算是個通常用到的coding技巧不難,但是吃重數學的思考跟小技巧的領域。
意思是如果你會coding量少到讓你開心?
"%"
給你兩個數字\(a, m\)
計算\(a\)「除以」\(m\)的結果
\(a = b \times m + r\)
這裡特別有\(0 \le r \le m-1\)
我們會說\(a,b\)在\(m\)下同餘意思是,
\(a,b\)除以\(m\)的餘數相同
數學上我們會用
\(a \equiv r \bmod{m}\) 表示
在\(\mod{m}\)之下
通常我們會用餘數\(r\)來分類整數們
因為\(0 \le r \le m-1\)
所以我們可以把整數分成\(m\)類
我們可以寫\(\mathbb{Z}_m\)
來表示\(\mod{m}\)下的同餘系
在C++裡面可以用「%」來算出餘數
a % m = r但要特別特別注意的是,在C++裡面當\(a\)是負數時
a % m = r所獲得的r
\(-m+1 \le r \le 0\)
Greatest Common Divisor
通常數學上會寫為\(\gcd\)
Least common multiple
對於兩個數字\(a, b\),有\(ab = \gcd(a,b) \times lcm(a,b)\)
要計算GCD我們會使用輾轉相除法
用到性質如果\(a=br +c\)
會有\(\gcd(a,b)=\gcd(b,c)\)
有趣的是\(a \ge b, b > c\)
這樣遞迴下去就會得到最大公因數
int gcd(int a,int b){ if(b==0) return a; else return gcd(b,a%b); }
std::__gcd(a,b)
我們很常要算\(a^n \mod{m}\)
當然可以直接\(O(n)\)一個一個乘
但是\(n\)很大就糟了
因此我們需要快速冪
巧妙地利用\(1,a,a^2,a^4,a^8 \ldots\) 等\(\bmod{m}\)的結果,來算出\({a^n}\bmod{m}\)的答案
答案初始值設為1,將\(n\)按照二進位分拆
如果\(n\)在二進位的第\(i\)的數值是\(1\),那我們就幫答案乘上\(a^{2^{i}}\)
int pow(int a, int b, int mod) { int ans = 1, tmp = a; while(b) { if(b&1) ans = ans * tmp % mod; tmp = tmp * tmp % mod; b >>= 1; } return a; }
我們常會想要在模底下求某個數字的反元素
也就是找到\(a^{-1}\)使得\(aa^{-1} \equiv 1 \mod{m}\),
如果在\(\mod{m}\)底下\(a\)要有反元素,就必須有\(\gcd(a,m)=1\),
對於任意整數\(a, m\),存在整數\(x,y\)使得
\(ax+my=\gcd(a,m)\)
如果今天\(\gcd(a,m)=1\),那就會有,\(ax+my = 1\)
也就是\(ax \equiv 1 \bmod{m}\)
此時的\(x\)其實就是\(a\)的反元素,
因此可以改進GCD的計算,讓我們計算出\(a\)的反元素。
int extgcd(int a, int b, int &x, int &y) { int d = a; if(b!=0) { d = extgcd(b, a%b, y, x); y -= (a/b)*x; } else x=1, y=0; return d; }
一個整數的因數們是整數重要的性質
如果想要計算一個數字的所有因數
可以利用一個重要的性質
如果\(a=xy\),則會有 \(x\le \sqrt{a}\) 或者 \(y \le \sqrt{a}\)
所以只需要檢查所有小於等於\(\sqrt{a}\)的數字
就可以算出\(a\)所有的因數,複雜度為\(O(\sqrt{a})\)
vector<int> factor(int a){ vector<int> res; for(int i=1;i<sqrt(a);i++){ if(a%i==0){ res.push_back(i); res.push_back(a/i); } } int s=(int)sqrt(a); if(a%s==0)res.push_back(s); }
「如果\(a=xy\),則\(x\le \sqrt{a}\) or \(y \le \sqrt{a}\)」
常常可以利用這個性質做出複雜度根號的做法
質數在數論中可是很重要的東西
從因數的觀點來看是一種整數的單元
而在模質數\(p\)底下除了0以外的所有數字都存在模逆元!
就是我們昨天講到\(\mathbb{Z}_m\)是個Filed這件事(還記得嗎?)
這代表著一件有趣的事,我們可以在模\(p\)底下進行「除法」。
想要把整數用質數來處理,當然要先知道要怎樣判斷一個質數
如果我們想要判定單一個數字是不是質數,可以簡單地利用因數分解,只要一個數的因數只有1和自己就是質數,
bool isPrime(int a) { if(a==1) return false; else return (factor(a).size() == 2); }
利用剛剛寫過的因數分解,需要\(O(\sqrt{a})\)的時間判斷。
最後我們會介紹更快驗質數的方法
如果今天想要重複的問不同數字是不是質數
我們可以開個陣列存某個數字是不是質數,利用篩法來算出有哪些質數
每遇到一個質數\(p\),我們就把所有\(p\)的倍數\(2p, 3p \ldots\)在陣列中都標記成\(false\),將它們「篩掉」
一樣每個正整數只需要知道自己「根號」以內的數字都不整除自己,該正整數就是個質數
因此只需要篩到\(\sqrt{n}\)就可以確定\(n\)以內有哪些是質數
給定整數\(n\),請問有多少質數小於\(n\)?
int prime[N], ptop, numOfPrime[N]; bool isPrime[N]; void init(){ pTop=0; for(int i=2;i<N;i++)isPrime[i]=true; for(int i=2;i<N;i++){ if(isPrime[i]){ prime[pTop++]=i; for(int j=2*i;j<N;j+=i)isPrime[j]=false; numOfPrime[i]=numOfPrime[i-1]+1; } else numOfPrime[i]=numOfPrime[i-1]; } }
而數學上對於質數個數有這樣個重要的結論
\(\pi(n) \approx \frac n {\ln n}\)
\(n\) \(\pi(n)\) 1 25 10 168 100 1229 1000 9592 10000 78498一個整數N我們可以分解成他質因數冪次的乘積
\(N = {p_1}^{e_1} \times {p_2}^{e_2} \times ... \times {p_n}^{e_n}\)
通常我們會用vector of pairs來表示\(N\)質因數分解後的結果
質因數個數約莫是\(O(\log{N})\)
我們可以掃過所有小於\(\sqrt{N}\)的所有質數,然後用while從把N除掉
但是這樣最差還是\(O(\sqrt{N})\)
有沒有辦法只看質因數個數次?
我們可以利用篩法,對於每個數字記錄他其中一個質因數
就是primeFactor[i]
if(isPrime[i]){ prime[pTop++]=i; firstPrime[i]=i; for(int j=2*i;j<N;j+=i) isPrime[j]=false,primeFactor[j]=i; numOfPrime[i]=numOfPrime[i-1]+1; }
讓我們回到質因數分解
typedef pair<int,int> PII; typedef vector<PII> VPII; #define pb(x) push_back(x) #define mp(x,y) make_pair(x,y) VPII factorInt(int n){ VPII res; while(n>1){ int p = primeFactor[n]; int e =0; while(n%p==0)n/=p,e++; res.pb(mp(p,e)); } return res; }
質因數分解可以做啥呢?
因數個數\(d(N)\)
\(d(N) = \prod\limits_{i=1}^n {(e_{i}+1)}\)
尤拉函數 \(\varphi(N)\)
\(\varphi(N)= N \times \prod\limits_{i=1}^n {(1- \frac{1}{p_i})}\)
我們可以利用質因數來做排容原理。
給定整數\(N,K\), 請輸出一個答案表示在\([1,N]\)之中與\(K\)互質的整數個數。
想要計算一個數字\(k\)在\([1,N]\)中有多少倍數
可以簡單地用\([N/k]\)做到
從質因數\(S = \{p_1,p_2,...,p_n\}\)中,窮舉所有的非空子集合
對於任意一種集合\(s\),計算\([1,N]\)中有多少是這個\(s\)裡面質數乘積的倍數
然後乘上\((-1)^{|s|}\)加起來
所以答案其實是
\(N - (\sum [N/p_i] - \sum [N/(p_ip_j)] + \sum [N/(p_ip_jp_k)]......)\)
typedef vector< pair<int, int> > VPII; int solve(int N, int K) { VPII factor = factorInt(N); int ans = 0; for(int i = 1; i<((1<<factor.size())-1) ; i++){ int tmp = 1, now = i; int count = 0, idx = 0; while(now){ if(now&1){ count++; tmp *= factor[idx].first; } idx++, now>>=1; } int s = (count%2 == 0) ? 1:-1; ans += s*(N/tmp); } return N - ans; }
給定\(N\)和\(K\)個小於等於\(N\)的質數 \(p_1 \ldots p_K\) ,\(N\)代表有標號\(1\)到\(N\)的\(N\)盞燈,每個質數\(p_i\)是一個開關,按下去的瞬間會把 \([1,N]\) 之中所有是這個質數倍數的燈會狀態改變,亮的變暗,暗的變亮。如果一開始所有燈都是暗的,你可以自由地使用質數開關,請問最後最多可以有多少燈是亮的呢?
\(a^p \equiv a \bmod{p}\)
特別的是如果\(\gcd(a,p)=1\),可以有
\(a^{p-1} \equiv 1 \bmod{p}\)
如果我們想要計算一個整數\(a\)在\(\mod p\)之下的反元素\(a^{-1}\)
在\(\gcd(a,p) = 1\)的狀況下我們知道,\(a^{p-1} \equiv 1 \bmod{p}\)
有趣的是\(a\times (a^{p-2}) \equiv 1 \bmod{p}\)
所以\((a^{p-2})\equiv a^{-1} \bmod{p}\)
int inverse(int a, int p){ return pow(a, p-2, p); }
尤拉函數是在計算對於一個整數\(N\), 小於等於\(N\)的整數,且跟\(N\)互質的有多少個,常用\(\varphi(N)\)表示。
\(\varphi(N)= N \times \prod\limits_{i=1}^n {(1- \frac{1}{p_i})}\) \(\prod\limits_{i=1}^n {{p_i}^{e_i-1}*(p_i-1)}\)
給定整數\(N, d\),請問\([1,N]\)中有多少整數\(i\), \(\gcd(i,N) = d\)呢?
因為\(\gcd(i,N)\)整除\(N\),所以如果\(d\)不是\(N\)的因數就答案一定是0。
如果\(N\)是\(d\)的倍數,那 \(N/d\)就會是個整數。
又因為\(\gcd(i,N)=d\),所以\(i\)一定是\(d\)的倍數,在\([1,N]\)中其實只有\(d, 2d ...,j\times d, ..., (N/d) \times d\)有可能是\(i\),讓我們重新回到等式並做些化簡
\(d = \gcd(j\times d, N) = d \times \gcd(j,N/d)\)
從上面等式中我們會發現我們其實要找
\(j \in [1,N/d]\)且\(\gcd(j,N/d) =1\)
這樣的個數就是
「小於等於\(N/d\)且與其互質的正整數個數」
\(\varphi(N/d)\)
尤拉定理是比較general版本的費馬小定理,如果\(\gcd(a,n)=1\)
\(a^{\varphi (n)} \equiv 1 \bmod{n}\)
特別當\(n\)是質數時是費馬小定理。
如費馬小定理一般,若\(a\)與\(n\)互質, 我們可以利用尤拉定理求出\(a\)在\(n\)下的模逆元, 也就是\(a^{\varphi(n)-1}\)。
在討論RSA之前,我們應該先來談談什麼是加密
通常就是兩個人想要傳訊息,但是有不想讓中間的人看到的訊息
最常見的加密方法都會有公鑰與私鑰,我們會將公鑰傳給「會傳東西給我們」的人,私鑰自己保管好
如果要傳訊息,那就是擁有公要的人用公鑰把東西加密,我們收到之後用私要解密
如果我們要傳送\(m\),我們可以先把它分拆成每個單元都小於\(N\)的數字\(n\)
對於一個\(n\)我們把東西加密成\(c\)
\(n^e \equiv c\ (\mathrm{mod}\ N)\)
然後把東西送出去
收到東西之後我只要計算
\(c^d \equiv n\ (\mathrm{mod}\ N)\)
就可以還原出\(n\)
\(c^d \equiv n^{e \cdot d}\ (\mathrm{mod}\ N)\)
以\(及ed = 1 (\mathrm{mod} p-1)和ed = 1 (\mathrm{mod} q-1)\)
由歐拉定理可證明(因為\(p\)和\(q\)是質數)
\(n^{e \cdot d} \equiv n\ (\mathrm{mod}\ p)\)和\(n^{e \cdot d} \equiv n\ (\mathrm{mod}\ q)\)
這說明
\(n^{e \cdot d} \equiv n\ (\mathrm{mod}\ pq)\)
對於質數\(p\)來說,
\((p-1)! \equiv -1 \bmod{p}\)
\(C(n,m) = \frac{n!}{m!(n-m)!}\)
預處理 f[i] ,invf[i]。
\(C(n,m) = C(n-1,m) + C(n-1,m-1)\)
整數在\(\mod{m}\)下是一個元素有限的集合,又寫作\(\mathbb{Z}_m\)。
所以當一個整數\(a\),把他的所有次方\(1, a, a^2, a^3 \cdots\)列出來, 因為這數列可以無限的下去,鴿籠原理告訴你說一定存在兩個不同的\(i,j\),其中\(i<j, a^i \equiv a^j \mod{m}\),對於任意大於等於\(i\)的整數\(k\)
\(a^k \equiv a^{k-i}a^{i} \equiv a^{k-i}a^{j}\)
\(\equiv a^{k +(j-i)} \mod{m}\)
因此對任意這樣的\(k\)來說, 任何正整數\(m\)都會讓下面等式滿足,
\(a^{k} \equiv a^{k+ m\times(j-i)} \mod{m}\)
這時候其實我們就找到了所謂的循環節!
Recall: 費馬小定理 或是 尤拉定理
再來是只要從\(1, a, a^2, a^3, a^4...\)找下去,
我們可以用map去記錄看過的數字
在\(O(\log{m} +\varphi(m))\)的時間內一定會找到最短的循環節,
簡單的解釋\(\log{m}\)的存在, 一開始不一定會出現循環節的原因, 其實是因為那些\(a\)與\(m\)之間共有的質因數。
如果那些質因數在\(a^i\)次方夠大時超過了\(m\)裡面同樣質因數的次方數, 那該質因數對於mod就不再會有影響, 所以約莫要花上\(\log{m}\)的時間來做到這件事。
這兩個結論告訴我們, 通常可以很放心的直接一個個跑下去找循環節, 很快就會找到。 很重要的是循環節通常會用兩個數字表示:
一個代表從哪個次方會開始循環。
一個代表循環節長度。
給定正整數\(a_1, a_2 ,a_3\), 再給定一個數字\(m\),
請問\(a_1^{a_2^{a_3}} \bmod{m}\) 的答案應該要是多少?
因為\({a_1}^{\varphi(m)}\equiv 1 \mod{m}\),
所以可以讓\(t\equiv{a_2}^{a_3} \mod{\varphi(m)}\),
然後就可以直接用\({a_i}^t\)算出答案。
但如果不互質的話,
我們其實可以去跑\(a_1\)在\(m\)中的起點與循環節,
假設起點是\(k\)循環節是\(r\)。
如果\({a_2}^{a_3} < k\)我們直接去計算答案就好,
因為\(k\)不會太大。
如果\({a_2}^{a_3} \ge k\)
那我們其實可以去計算\(t \equiv {a_2}^{a_3}-k \mod{r}\),
那答案就是\(a^k \times a^t \mod{m}\)。
對於 \(a^k \equiv x \mod{m}\)
如果 \(k > len\)
\(k \equiv k' \mod{phi(m)}\)
就算 \(a^{k' + phi(m)}\)
這個定理想必大家耳熟能詳?
孫子算經中曾有段話:
「今有物不知其數,三三數之賸二,五五數之賸三,七七數之賸二,問物幾何?」
答曰:「二十三」
或許這就是中國剩餘定理最早的來由吧。
劉邦想要知道韓信到底有多少能力可以帶兵打仗, 他想要考考韓信估計兵力的能力, 於是問個問題說:
「如果每個帳篷住十個人會剩下兩個,
如果每餐都有十一個人坐在火爐邊吃飯那就會剩下三人,
如果每個隊伍有十三個人就會有一小隊只有五人,
那這個軍隊中應該要有多少人呢?」
雖然韓信很聰明, 但想說如果每天劉邦都亂出一題新的來問就麻煩了, 所以想請你寫個程式來幫他算算答案。
劉邦出題的方式很固定,每次都是\(n\)個問句, 每個問句都是說如果\(m_i\)個人一團會剩下\(a_i\)個人, 這裡特別有\(m_i\)裡面兩兩互質, 因為不互質劉邦自己也不太會。
想請你算出最小可能的軍隊人數\(x\)是?
\(\left\{ \begin{matrix} x \equiv a_1 \pmod {m_1} \\ x \equiv a_2 \pmod {m_2} \\ \vdots \qquad\qquad\qquad \\ x \equiv a_n \pmod {m_n} \end{matrix} \right.\)
因為\(m_i\)之間兩兩互質,所以一定有解,
我們直接構造答案來證明這件事,這也順便給出了一種算出答案的做法。
首先設\(M = \prod_{i=1}^n m_i\) ,並設\(M_i = M/m_i\)。
然後我們可以計算\(t_i\)為\(M_i\)在\(\mod{m_i}\)下的模逆元,
也就是\(t_iM_i \equiv 1 \mod{m_i}\)。
因為\(M_i\)為除了\(m_i\)之外的\(m_j\)的乘積,所以與\(m_i\)互質,
這裡可以使用尤拉定理或是Extended Euclidean Algorithm來算出模逆元\(t_i\)。
最後答案\(\sum\limits_{i=1}^{n} a_it_iM_i \mod{M}\)就會是的結果。
為甚麼答案是對的呢?
因為對於任意的\(m_j\),
\(\sum\limits_{i=1}^{n} a_it_iM_i \equiv a_jt_jM_j \equiv a_j \mod{m_j}\)
然後\(M\)被任意的\(m_j\)整除。
這樣可以獲得一個\(O(n \log{m})\)的中國剩餘定理做法。
所謂的\(B\)進制,就是一個數每位的數碼是\(1\)到\(B-1\),
數碼逢\(B\)進位。
所以如果一個數字在\(B\)進位下是\(\overline{a_na_{n-1}...a_0}_{B}\),
那這個數字在十進位之下就是\(a_n \times B^n + a_{n-1} \times B^{n-1}+......+a_0\)。
進位制是在不只是在數論中會探討,
在程式語言中進位制也是個需要知道的知識,
我們平常在使用的「bit」就是把整數在電腦中做二進位轉換,
而我們也會常常看到所謂的HEX,就是數字在電腦中存成16進位的字串。
進位制可說是程式中的一種常識。
平常我們最常用的進位制就是十進制,
在這章節中我們用\(a_{(10)}\)來表示\(a\)的十進制數碼,
如果今天我們要把\(a\)從10進制轉成轉成\(B\)進制,
其實我們不久前才做過??
int BtoTen(int a_B[],int B,int top){ int res = 0; int base = 1; for(int i=0;i<top;i++){ res += base*a_B[i]; base *= B; } return res; }
給定一個數字在\(B\)進位下的數碼,
要還原成原本的十進位數字,
就直接照著進位制的定義可以算出結果。
\(a_n \times B^n + a_{n-1} \times B^{n-1}+......+a_0\)
int BtoTen(int a_B[],int B,int top){ int res = 0; int base = 1; for(int i=0;i<top;i++){ res += base*a_B[i]; base *= B; } return res; }
我們要談的是一種跟組合數計算有關的定理,
當我們要計算\(\binom{n}{m} \bmod{p}\)時,
如果\(p\)的數量級沒有那麼大,
我們可以利用這個定理快速的算出這個組合數的結果。
\(\binom{n}{m} \bmod{p} \equiv \prod\limits_{i=0}^{k} \binom{m_i}{n_i} \bmod{p}\)
\(m = \sum\limits_{i=0}^{k} m_i \times p^{i}, n = \sum\limits_{i=0}^{k} n_i \times p^{i}\)
特別的是如果\(m>n\),那我們讓\(\binom{n}{m} =0\)
這種遊戲中文常叫做「拈」。
是個經典的遊戲問題,
其結論也跟進位制有關係。
今天如果我們有\(n\)堆石頭,第\(i\)堆石頭有\(a_i\)個石頭, 兩個人輪流玩遊戲,不能拿的人就輸了, 請問先手還是後手會贏?
Nim Game的結論是今天我們把所以\(a_i\)轉成二進位,
對於每一位數統計1的個數,
如果每一位1個數都是偶數,那就是後手會贏,
其他是先手會贏。
輸的盤面是所有石頭堆都變為零,
每位的1個數都是偶數,
舉個例如果石頭有3堆,分別是\(3,4,5\),
他們二進制分別是\(011_{(2)}, 100_{(2)}, 101_{(2)}\),
第二位1的個數不是偶數,因此先手會贏。
要判定這件事,
其實就是看\(t = \bigoplus\limits_{i=1}^n a_i\)是否\(t=0\),
有趣的如果先手會贏,他的第一步就是尋找\(i\)使得\(a_i \oplus t < a_i\)的那堆\(i\),
然後把\(a_i\)拿成\(a_i \oplus t\),
這樣新的\(t' = \bigoplus\limits_{i=1}^n {a'}_i = t \oplus t = 0\),
因此我們讓後手拿到XOR為零的盤面,
也可以保證我們一定可以做到這件事。
\(N\)個人坐成一個圓,瞬時針編號為\(1, 2,\cdots, N\), 從1號開始開始順時針報數\(1,2,1,2......\), 只要數到2的人就會從圈圈中被消除, 請問最後活下來的是幾號呢?
如果用函數\(f(N)\)來表示N個人玩這個遊戲活下來的號碼, 那我們會得到下列遞迴式
\(2f(m)-1\) 如果 \(N = 2m\) 是偶數
\(2f(m)+1\) 如果 \(N = 2m+1\) 是奇數
這樣的\(f(N)\)的結論可以用進位置表示,
今天如果\(N = {b_nb_{n-1}...b_0}_{(2)}\),最高位\(b_n =1\),
那有趣的是答案會是\(f(N) = {b_{n-1}b_{n-2}...b_0b_n}_{(2)}\)。
希望每個數字都只被篩法篩到一次(原本篩法會被所有質因數遍歷)
這演算法設計上希望所有的合數\(m\)都只被最小的質因數篩到
因此我們改變篩法的方向,
對於每個數字\(m\)我們用那些小於它的那些質數\(p_i\)乘上\(m\), 去更新\(m \times p_i\),讓其被篩掉,
在\(p_i\)尚未整除\(m\)之前,\(p_i\)一定比\(m\)中最小的質因數還要小。
int p[N], np=0; int a[N]; for (int m = 2; m < N; m++) { if (a[m] == 0) p[np++] = m; for (i = 0; i < np && m*p[i] < N; i++) { a[m*p[i]] = 1; if (m%p[i] == 0) break; } }
只要一發現某個質數\(p_i\)整除\(m\),表示該質數是\(m\)最小的質數,
為了讓之後的數字都只被自己最小的質數篩掉
就該從迴圈中跳出繼續窮舉。
根據費馬小定理我們知道\(a^{p-1}\equiv 1 \mod{p}\),
對於一個未知的\(n\),
我們在區間\([1,n-1]\)中選擇很多個\(a\)來進行檢驗,
如果結果都是1那在費馬質數判定中我們會說他是質數。
費馬質數判定的缺點是
存在著叫做偽質數 (Carmichael Numbers)數字\(n\),所有\([1,n-1]\)中的\(a\)都會滿足費馬小定理的等式, 但是\(n\)卻不是質數。
首先將\(N-1\)分解為\(2^sd\)。
在每次測試先隨機選一個介於\([1, N-1]\)的整數\(a\),對所有的\(r \in [0, s-1]\), 若\(a^d \mod N \neq 1且a^{2^{r}d} \mod N \neq -1\), 則\(N\)是合數。 不然,\(N\)有\(3/4\)的「機率」為質數。
bool miller_rabin(LL n, LL a) { if (__gcd(a, n) == n) return PRIME; if (__gcd(a, n) != 1) return COMPOSITE; LL d = n-1, r = 0, res; while(d%2==0) { ++r; d/=2; } res = bigmod(a, d, n); //計算 a 的 d 次方除以 n 的餘數 if (res == 1 || res == n-1) return PRIME; while(r--) { res = mul(res, res, n); //小心乘法溢位 if (res == n-1) return PRIME; } return COMPOSITE; }
bool isprime(LL n) { LL as[7]={2, 325, 9375, 28178, 450775, 9780504, 1795265022}; for(int i=0;i<7;i++) if(miller_rabin(n, as[i]) == COMPOSITE) return COMPOSITE; return PRIME; }
LL as[7]={2, 325, 9375, 28178, 450775, 9780504, 1795265022};
其實在 \(2^{64}\) 以內,只要用這幾個 \(a\) 檢查過之後, 就可以完全確定範圍內的數字是否為質數了! 順帶一提,檢查 int 範圍內的質數只需要以 2, 7, 61 這三個數字為基底檢查就可以了。
把這幾個數字藏進 codebook 裡是個不錯的選擇唷!
因為是一定「機率」是質數,所以要做夠多次就幾乎確定該數字是否為質數。
但是沒有費馬質數判定法的問題,次數夠多一定可以把不是合數的數字篩掉。
31, 331, 3331, 33331, 333331, 3333331, 33333331都是質數。
但33333331就不是了。
形式為\(2^k -1\)的質數
前四個是\(k =2,3,5,7\),目前最大的有22,338,618位數。
\(k = 74207281\)。