數論[計算機數學專題(5)]

噠噠噠!掌握一種心理學的學習概念,人的認知是不斷成長的,不必要因爲一時的失意,而否定您。

數學不好,也沒關係,一起成長。早在出生起,我們每天笨拙的咿呀咿呀學漢語與走路,我們最終都學會了。爲什麼,因爲那時候的我們,不會在意別人的眼光,不會怕做的不好就不去練習。

來吧,短暫人生路,讓我們一起擁有手眼通天的膽量!更重要的是,如果您可以認真看完這裏的,數論可以算入門了,不過我會實時更新,常回來看看哈,RSA加密數論部分更新ing。


                                                                                          《目錄》


數論基礎前置知識:

整除:

  • 如果 a 被 p 整除,a 是 p 的倍數,記爲:p | a ,如  3 | 9 ,  1 | 9,  9 | 9; 其中 1、9 是9的頻繁因子 , 3是9的非頻繁因子。

傳遞性:

  •  如果 a 是 p 的倍數,那麼 a 的倍數也是 p 的倍數,如 2 | 4 , 4 | 12 . 故有 2 | 12,記爲  a | b, b | c, 則 a | c。

 

整數的質因數分解 :

  •   一個正整數拆分爲質數的冪的乘積,如 24 =  2 * 2 * 2 * 3 = 2^{2} * 3^{^{1}} , 17 = 17^{1}

  •   質因數分解採用短除法【小學知識】

 唯一分解定理 :【數論基石,也叫算術基本定理】

  •   正整數的質因數分解是唯一的 , n = p_{1}^{r1} * p_{2}^{r2} * p_{3}^{r3} ··· ,p_{i} 是質數,如上 24 和 17。

質因數分解看除法 :

如 ,12 = 2^{2} * 3^{1}  ,  180 = 2^{2} * 3^{2} * 5^{1} , 那麼;

   \frac{180}{12} = \frac{2^{2}}{2^{2}} * \frac{3^{2}}{3^{1}} * \frac{5^{1}}{5^{0}} = 2^{0} * 3^{1} * 5^{1} = 15

易發現,正整數的除法,即把倆個數的質因數分解,接着每個質數的質數相減。

 質因數分解看整除 : 

 m | n , 顯然 是當且僅當 n ➗ m 是整數。[ 4 | 12    =>    12  ➗ 4 ]

解釋一下,如果 a_{i} - b_{i} < 0,那麼算出來的就是一個分數,不是整數。如,2^{-1}。 

  學了這些,我們就可用這個代替整除,把 n 和 m 質因數分解,然後對比指數。

  如果 n 每一個質數的指數都大於等於m 的對應指數,那麼 n 就是 m 的倍數 !!

 編程,只學理論,不敲代碼;都是耍流氓

 題目:20 * 30 * 50 / 60 * 70 / 80 * 90 和 7 是否 相等。

             3 min ...

 那麼,暴力高精度 O(n^{2}) , n 是數字長度,可是太慢。用 double 計算,如果有上萬次運算,精度無法保證。

 需要指出,我們可以採用 剛剛學習的質因數分解方法解,您看最大數 90,90以內的素數只有 24 個。[利用容斥原理也可以得到素數個數 , N = (P'_{1}+P'_{2}+P'_{3}+P'_{4}) + 4, \sqrt90 = 9, 小於9的素數只有4個, 2、3、5、7,解出來]

我們把每個整數分解成質數的冪,然後存儲這些指數。

乘法 : 指數相加   [如果是加法,那隻能高精度了]

除法 : 指數相減   [如果是減法,那隻能高精度了]

判斷是否相等 :  判斷每一個指數是否相等 [唯一分解定理]


  • #include <iostream>
    using namespace std;
    const int n = 24;
    int prime[n] = {2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89};
    
    struct Hi
    {
    	int flag, r[n];   // r[] 存儲每個質數的指數
    	
    	 friend Hi operator * (Hi a, Hi b)   //  指數加法
    	{
    		Hi ans;
    		for( size_t  i = 0; i < n;  i ++ )
    		      ans.r[i] = a.r[i] + b.r[i];
    		return ans;
    	}
    	
    	friend bool operator != (Hi a, Hi b)  // 判斷指數是否相等
    	{
    		for( size_t  i = 0; i < n;  i ++ )
    		      if( a.r[i] != b.r[i] )  return true;
    		return false;
    	}
    };
    
    int main(void)
    {
       // 同九義汝何秀
       
       return 0;
    }

NO. 1 約數篇[因數]

  1. 整數 a(4) 能被整數 b(2) 整除,a(4) 叫做 b(2) 的倍數,b(2) 就叫做 a(4) 的約數。
  2. 約數個數是有限的,記爲 d(n) (自然數是這樣 - 從0開始的整數)
  3. 所有數的約數都包含 自身 和 (或) 1                                       

 借 圖.jpg 獻計:

        請看,n = 1 , d(n) = 1   =>   1 的約數有且只有一個即 1;

                   n = 10 , d(n) = 4  => 10 的約數共 4 個即 1、2、5、10;

                   其餘數同。

那麼想一想,我們怎麼把這個表格打出來??  莫急莫急,要成長,就需要建立新的神經元連接,數學與思考正是良計!!


​​#include <stdio.h>
#define N 1000
int d[N];  // 默認爲 0 ,存儲在 .data 段

// 計算 [1,n] 的約數個數 O(n log n)
void fews(int n){
    for( int i = 1; i <= n; i ++ )
    {
    	for( int j = i; j <= n; j += i ) // 枚舉 i 的所有倍數,換一個角度想,本來是約數
    	     d[j] ++;
    }
}

// 【人之千慮,比有一得 ~ ,您的方法是怎麼樣的,推敲一遍】
int main(void)
{
	int cnt = 0;
	int n;
	scanf("%d",&n);
	fews(n);
	for( int i = 1; i <= N; i ++ )
	    if(d[i])  cnt++,printf("%-3d%c",d[i],cnt%10 ? ' ' : '\n');
    printf("\n[1,n] 每個數約數表");
	return 0;
}

下面選修 ~

數論的基石,唯一分解定理解析約數個數。 

一個數 p = 2^{^a} * 3^{b} * 5^{c} ··· ,且 [a , b , c] > 0,   如 180 = 2^{2} * 3^{2} * 5^{1} 

這裏指數一一對應, a 可取 {0,1,2} , b 可取 {0,1,2} , c 可取 {0,1} , (a,b,c) 一共3 * 3 * 2 = 18種取法,每種取法對應一個約數。

            e.g.   a , b , c 都取 1 時,2^{^1} * 3^{1} * 5^{^1} = 30 ,而 30 就是 180 的一個約數。

所以,如果一個數 n 可以分解   爲     n = p_{1}^{r_{1}} * p_{2}^{r_{2}} * p_{3}^{r_{3}} ··· , 則 ta 的約數有  d(n) = (r_{1}+1) (r_{2}+1)(r_{3}+1) ···

           e.g.  180     r_{1} = r_{2} = 2 , r_{3} = 1 ,  d(n) = (2+1) * (2+1) * (1+1)  = 18 種。

 OK,歡迎在評論區或者郵件分享您的代碼哦·簡單的理解了約數和倍數,接下來學習的素數,是數論裏的?,非常重要。

          許許多多的理論與問題都圍繞素數建立,如

  • 被用於電影,安全碼,謎題,甚至是大學教授的孤獨主題
  • 哥德巴赫猜想
  • 是否有無窮多個梅森素數
  • 第六代加密算法 - RSA非對稱加密法 [手機支付加密、網銀加密]
  • 斐波那契數列是否存在無窮多個素數
  • 費馬大定理是否成立
  • 黎曼猜想
  • 孿生素數是否有無窮多組 [間隔爲2,如 (3,5) , ... ,(71,73) , ...]
  • 中國剩餘定理 [韓信點兵、物不知數]
  • 自然界多數生物的生命週期(年)是素數,最大限度減少碰見天敵的機會[蟬昆蟲每隔13或17年從地面發芽]
  • 機械中齒輪的齒數是素數,以增加倆⚙️倆個相同齒相遇齧合次數的最小公倍數 [提高了耐用度, 減少故障]
  • ... ...

可見素數不是一般的重要,上面列舉出來的,程序員是要學習與理解的,只爲優質的算法效率足以。

P.S. 我會將上面的問題,都解出來,不過,大概需要一些時間。star ~


什麼是素數

         數學定義: 素數, 又叫質數, 是指在一個大於 1 的自然數中, 除了1 和 自身外, 無法被其他自然數整除的數。

         p.s. 1 、0, 即非素數又非合數,素數也是 "不可分割數" 。

  •  Primes是所有數字的基石。就像在化學中一樣,瞭解材料的化學結構有助於理解和預測其性質。
  •  Primes具有特殊屬性,如難以確定(是的,即使是困難也可能是一個積極的特徵)。這些屬性可用於加密,循環以及查看其他數字如何相乘。

        小學生都明白的概念,可是,從古至今,多少數學家都想能明白素數的排列的規則,卻找不到其具體的分佈的規律,只知道是螺旋排列,如下圖

 每一個黑點都是一個素數 ~

       介紹一個程序裏算法常模質數 : 19260817。在計數問題中,由於產生的數據規模非常大[高精度問題],這時候要規定一個範圍,通常情況是模一個素數。大部分情況 選 19260817 就好,大小合適,也是質數。

       目前我知道最大的素數是 2017年12月全球合作項目 "互聯網梅森素數搜索" 中由 現年51歲的田納西州的電氣工程師Jonthan Pace 在自己電腦上,發現了M77232917,即 2的77232917次方 - 1。嘖嘖,不愧是電氣工程師,電腦配置肯定槓槓的 ~

 素數驗證方法:


         方法一: 枚舉 2 ~ n-1, 如果 n % i == 0, 則 n 爲合數
             代碼略 ... ...  O(n²);


         方法二: 優化方法一, 若一個數是合數, 則必有一個小於等於ta的平方根的因數 O(n√n)
             比如, n = 15, n 的素數有 1、3、5, 除數5因爲再 n 被3整除時, 其商是5, 也就表示 n 能被 5 整除。

 


#include <stdio.h>
#include <math.h>

bool is_prime(size_t n)
{
	size_t qn = sqrt(n);
	for( int i = 2 ; i <= qn; i ++ )
	    if( qn % i == 0 )
	        return false;
	 return true;
}

int main(void)
{
	printf("%d\n",is_prime(1));
	return 0;
}

 


加油加油 ~  


方法三: 埃拉托色尼篩  O( n log logn )

一個合數可以被一個不超過 ta 的平方根的素數整除,如果,爲找出不超過 100 的素數的個數,首先注意不超過 100 的合數一定有一個不超過 10 的素因子。由於小於 10 的素數只有 2 、3、5、 7,因此不超過 100 的素數就是這 4 個素數以及那些大於 1 和不超過 100 且不被 2、3、5、7 整除的正整數。


               把 n 個自然數按次數排序,可用計數數組。
               step-0: 篩去 0、1 , 先算出 根號n 的長度,就不用每次循環 i*i 或者 sqrt(n) [優化步驟, 可省略]
               step-1: 篩去 合數, 減少一半時間
               step-2: 篩去 2的倍數
               step-3: 篩去上一步沒篩的第一個數(3)的倍數
               step-4: 篩去上一步沒篩的第一個數(5)的倍數 [如果沒去合數,那麼就是4, 下面同理,所以說減一半時間]
               step-5: 篩去上一步沒篩的第一個數(7)的倍數
               step-6: 第一個劃掉的數字總是當前這輪所用素數的平方, 避免重複判斷[優化步驟,可省略]
               step-7: 一直循環, 最後留下來的就是不超過n的素數 

請看動圖

如,i 爲 24 時,i 模了 2, 3 (沒優化合數和模4) 。

#include <stdio.h>
#include <math.h>
#define Max 1000
int flag[Max];	// 默認爲0, 存儲在 .data

void Init(int *a);
void Eratosthenes( size_t n );	// O( n log(logn) )


int main(void)
{
    size_t cnt = 0;
    size_t n = Max;
    Init(flag);
	Eratosthenes(n);
    
   printf("\t\t[2,%d] 素數表:>\n\n",Max);
	for ( int i = 2; i < Max; i++ )
		if( !(flag[i]) )
		     cnt++, printf("%-5d%c", i, cnt % 8 ? ' ': 10);

	printf("\n\n%d %s %u", Max, "以內的素數共", cnt);

	return 0;
}

void Eratosthenes( size_t N )
{
	for (int i = 2; i * i <= N; i++)	// 試除 [2,√n]
	{
		if ( !(flag[i]) )			// 篩選素數的標誌
		{
			for (int j = i * i; j <= N; j += i)	// 篩去 i * n,即 i 倍
// 每一輪篩選時, 第一個劃掉的數字總是當前這輪所用素數的平方
			{
					flag[j]++;
			}
		}
	}
}

void Init(int *a) // 去合數,優化步驟 step-0
{
	for( int i = 4; i * i <= Max; i += 2 )  // 合數形式 2n 
	    if( i & 0x1 == 0 )   // 同 % 2
	         a[i] ++;
}

歐拉篩法(線性篩)  :過程同埃篩,不過只模最小質數。 加油,下面有舉例。不過,我先講清楚原理,您認真看上面的動圖,可以發現,會有重複篩除的合數。比如 30,在 i = 2 時,會篩30,i = 5 時,會篩30,所以就有了線性篩。

#include <stdio.h>
#include <math.h>
#define Max 1000

int flag[ Max ], cnt;		// 默認爲0, 存儲在 .data
void Init(int *a);
int juge[ Max ];
void Euler( size_t N );          // O( n )

int main(void)
{
    size_t cnt = 0;
    size_t n = Max;
    Init(flag);
    Euler(n);
    
   printf("\t\t[2,%d] 素數表:>\n\n",Max);
	for ( int i = 2; i < Max; i++ )
		if( !(flag[i]) )
		     cnt++, printf("%-5d%c", i, i % 8 ? 32 : 10);

	printf("\n\n%d %s %u", Max, "以內的素數共", cnt);

	return 0;
}

// 改動部分
void Euler( size_t N )
{
	for( int i = 2;  i <= N;  i ++ )  // 枚舉每個數
	{
	    if( !flag[i] ) juge[cnt++] = i;  // juge[] 存儲素數
	        for( int j = 0; i * juge[j] <= N;  j ++ )   // 枚舉已經篩好的素數
	        {
	             flag[i * juge[j]] ++;   // 篩去合數, 任何合數都可以寫爲多個素數積
	             if( !( i % juge[j] ) )  
	                 break;  
	             // 保證 p[j] 是 i * p[j] 最小的素因子, 每個數只被最小質因數篩
	        }
	}
}


void Init(int *a)   // 去合數
{
	for( int i = 4; i * i <= Max; i += 2 )
	    if( i & 0x1 == 0 )   // 於 % 2
	         a[i] ++;
}

對於上面的程序只改動了這一個接口,這一接口相對上面的埃篩,不同的是最後取模 if(..) break 因爲ta 保證取最小素數模。

如,i 爲 24 時,i 只模了 2; 還有 3, 4, 並沒有模。

 

方法五,比 歐拉篩 更快。 思路,空間換時間

倆種實現,第一種,把目前所有知道的素數按順序存儲在一個數組裏 [大數存儲要設計算法]

                  第二種,把一定範圍的素數按照數組下標緩存,是素數,那麼對應下標設爲0 [個人愛好]。

  說到空間,這裏有一種節約空間的方法。位圖篩法求素數,代碼拿出來好好研究(在理解埃篩後補習一下位操作)。

#include <stdio.h>
#include <stdlib.h>
#define SHIFT  5           // 2的5次方, 一個int佔32位
#define MASK  0x1F         // 0x0001 1111

const int N = 100;      
int state[ (N>> SHIFT) + 1 ];     // 申請 100 位 (N>>SHIFT):100-(32*3) = 4 bits
int totalPrime[N];                      // 記錄有多少個素數

bool isPrime( int x )
{  return !( state[x>>SHIFT] & (1<<(x & MASK) ) );  }    // 獲取結果,0爲素數 接着取反 !0 = 1, 所以打印時素數變成 1  

void Prime( void )
{
    memset(totalPrime, 0, sizeof(totalPrime) );     // 記錄素數的數組清零
    memset( state, 0, sizeof(state) );              // 狀態數組清零
    state[0] |= (1<<0);                             // 設置 0 爲合數類(規定 1 不是素數)
    state[0] |= (1<<1);                             // 設置 1 爲合數類(規定 1 不是素數)

    
	for( size_t i = 2; i < N; i ++ )
	{
		totalPrime[i] = totalPrime[i-1];            // 因爲記錄是一個數組,所以需要加上之前統計的
		if( (state[i>>SHIFT] & (1<<(i&MASK) ) ) )   // 獲得狀態,是 真 即合數
             continue;
		for( size_t j = i*i; j < N; j += i )
		    state[j>>SHIFT] |= (1<<( j&MASK) );     //  位設置
		    
		if( isPrime(i) )                            //  如果是素數計數++
		    totalPrime[i] ++;
	}
}

int main(void)
{
	Prime( );
	size_t cnt = 0;
	for( size_t i=0; i<N; i ++ )
	    cnt ++,
	    printf("%4d : %-4d%c", i, isPrime(i), cnt&3?'\t':'\n' );  // cnt&3 == cnt%4
	printf("\n  共 %d 個素數",totalPrime[cnt-1]);
	return 0;
}

 

2019,新年快樂,祝一切順利。

當然,如果對素數有興趣。可以參加全球合作項目 "互聯網梅森素數搜索"GIMPS。1996年迄今爲止,ta 共找到了16個梅森素數。

       Jonthan Pace 獲得了 3000 美金,雖然不多但很有意義。發現更多的素數,可以幫助數學家更深入的理解,素數是在上面情況下出現。

【賞金】 GIMPS:

  •   誰第一個找到 1 億位的素數會有 15 萬美元的獎勵
  •   誰第一個找到 10 億位的素數會有 25 萬美元的獎勵

目前,找素數都是分佈式網絡,許多的計算機聯合暴力計算完成。因爲,誰也沒有發現素數具體的分佈規律,也沒有特定的公式,所以找素數的任務交給了 GIMPS。 

 

下面正式進入素數的世界【反證法,證明素數是無窮多個

       預先給定幾個素數,則有比他們更多的素數。

      設 a, b, c 爲給定的素數, 構造一個數 A 等於 給定素數相乘變爲合數再加一

       形如, A = a * b * c + 1

       那麼 A   可以質因數分解嘛 ?[表示成上述的質數他們的乘積]

 

      很顯然,我們用其中任何一個素數均不能整除 A 。

  • 要麼 A 本身是一個素數,那麼 A 就不等於 a , b , c 任意一個數。
  • 要麼 A 能被不同於 a , b , c 的某個數整除。

無論,上述哪種情況。必然存在一個素數 s 不同於已有的素數a, b, c ;

  • 2 * 3 * 5 + 1 = 31
  • 3 * 5 * 7 + 1 = 106 = 2 * 53

也就是說,有了 n 個素數,就可以構造出第 n + 1 個素數,因此素數有無窮多個。

素數是有限的,這不是謬論嗎?

      所以,質數數是無窮的!!質數存儲在螺旋數字裏的分佈也只是模糊的螺旋狀。相信,當我們知道的素數達到一定界限時,我們可以找到一條定理來描述素數分佈的規律,ta實在太重要了,對數學家,編程愛好者都是如此。

-------------------------------------------------------------------------------------------------------------------------------

威爾遜定理趣談

     有人說,如果一個人不知道威爾遜定理,那麼ta的 算術 算是白學了!

誇張嗎 ~ 我覺得是有戲劇性的。那您可知道,ta爲什麼這麼說呢??事實上,威爾遜定理是萊布尼茨首先發現的,後經拉格朗日證明的;威爾遜(這裏是人名,英國法官J.  Wilson)的一位擅長拍馬屁的朋友 沃潤(E.  Waring) 於 1770年出版一本書裏說,這條定理是威爾遜發明, 而且對外說這條定理永遠不會被證明(因爲缺少好的符號來處理素數),這話被高斯聽見了,當時高斯也不清楚拉格朗日證明了這條定理。站在 blackboard 前 5 分鐘,就證明了這條定理。所以,高斯說,威爾遜差的不是符號,而是概念。

     故事說完了,定理我們展開慢慢說。

-------------------------------------------------------------------------------------------------------------------------------

     威爾遜定理 :

  •          若 p 爲素數, 則  (p-1)!\equiv -1(mod~p)  (其中 !表示  階乘),

     則威爾遜定理也成立,即: 

  •          若對某一正整數 p,有 (p-1)! + 1 一定是 p 的倍數,所以再利用 sin 函數的特點,可以構造出一個素數分佈的函數曲線  f(n) : f(n) = \sin (\pi*((n-1)!+1)/n)   這個函數值爲 0 的點都是素數所在的點。

     稍稍解釋一下,如果看了不明白,那麼看一下同餘。

     威爾遜定理應用很廣,例如對較大的質數 p,我們雖然無力算出 (p-1)! 的值,但卻知道 (p-1)! 被 p 除但餘數是 -1 或 p - 1。事實上,由於 (p-1)! + 1 可被 p 整除,則存在自然數 n,使得 (p-1)!  + 1 = np,(p-1)! = np - 1 = (n-1)p+(p-1),所以 (p-1)! 被 p除的餘數是 -1 或者 p - 1。

     我假定,數論您是零基礎的,沒關係,我們一起解讀。

        先學習一個同餘的概念,選修 ~

        32 \equiv 2(mod~5)  ,ta 意味着 32 - 2 = 5 * n, n =》6

     若 a, b 爲倆個整數,且它們的差 a - b 能被某個自然數 m 所整除,則稱 a 就模 m 來說同餘於b,或者說 a 和 b 關於模 m 同餘,記爲 a \equiv b(mod~m),等同 a - b = m * k (k是整數)  。 

        (p-1)! \equiv -1(mod~p) 

 推出: (p-1)! + 1  =   p * n,OK 同餘補習好了,請看上面的紅色推導部分吧。

 

         如果我們要判斷 p 是否是素數,那麼就看 p 滿不滿足  (p-1)! \equiv -1(mod~p) ,滿足就是質數。

#include <iostream>
using namespace std;

const int Q = 20;


void factorial(long long (&fac)[Q] )  // 傳引用, 求階乘
{
	for( size_t i=1; i<=Q; i ++ )
	{
	     fac[i] = fac[i-1] * i;
	}
}
// 可以換 預處理階乘 或 快速乘


bool check( long long *fac, size_t p )   // p 爲一個正整數
{
  if( fac[p-1]%p == p-1 )    // fac 階乘數組 [公式: (p-1)! + 1 = p * n]
     return true;     
  return false;
}
// 威爾遜逆定理 , O(n) 容易爆 long long


int main(void)
{
	long long fac[Q] = {1,1};
	factorial(fac);
   for( size_t i=2; i<=Q; i ++ ){
       if( check(fac,i) )
           cout<<i<<" ";
   }
	return 0;
}

其實,這定理。有點費呀,n! 階乘,對於計算機的內存都是 很傷的,按照 一位算法工程師角度說,這應該是一個無效的壞算法!

下面,是證明威爾遜定理的一種方法。[學會證明,是數學學習的一種很好的偷懶方法因爲高效]

設 p 是素數,p = 2 時,定理成立不足道。考慮到所佔空間較多,我在博客最後證明。

 

-------------------------------------------------------------------------------------------------------------------------------

  GCD 最大公約數 與 LCM 最小公倍數

           有倆個數 n and m,ta們的最大公約數 gcd(n,m) ,滿足 d | n 與 d | m 的最大值 d。

   那麼 d 是多少 ? 如果從質因數分解的角度說,d的指數要儘可能大,但得小於等於 n 和 m 的最小指數[如果選 n 和 m最大指數,就是 最小公約數]。

         e.g.   200 = 2^{3} * 3^{0} * 5^{2}

                  60 = 2^{2} * 3^{1} * 5^{1}

                  gcd(200,60) = 2^{min(3,2)} * 3^{min(0,1) } * 5^{min(2,1)} = 2^{2} * 3^{0} * 5^{1} = 20                  

GCD(n,m) =》 n 和 m 質因數分解,於是每個質數取其在 n , m 中最小指數。

 

                  lcm(200,60) = 2^{max(3,2)} * 3^{max(0,1) } * 5^{max(2,1)} = 2^{3} * 3^{1} * 5^{2} = 600

LCM(n,m) =》 n 和 m 質因數分解,於是每個質數取其在 n , m 中最大指數。

 

                  下面講解,gcd 證明,選修 ~

   xxx

 

                  編程,只學理論,不敲代碼;都是耍流氓

 

   倆個數的 GCD 我們用 歐幾里得算法,LCM 這裏有一個很漂亮很漂亮的性質。

   GCD(n,m) * LCM(n,m) = n * m

                  下面證明,爲什麼是這樣的,選修 ~   [前置技能 :  質因數分解]

                 GCD(n,m) = 2^{min(n_{1},m_{1}) } * 3^{min(n_{2},m_{2})} * 5^{min(n3,m3)}    ··· 

                LCM(n,m) = 2^{max(n_{1},m_{1})} * 3^{^max(n_{2},m_{2})} * 5^{max(n_{3},m_{3})}   ···

      GCD(n,m) * LCM(n,m) = ( 2^{min(n_{1},m_{1})} * 3^{min(n_{2},m_{2})} * 5^{min(n_{3},m_{3})} ) + ( 2^{max(n_{1},m_{1})} * 3^{max(n_{2},m_{2})} * 5^{max(n_{3},m_{3}) )

               p.s. 實際不止 2 , 3 , 5 ,還有更多素數,不過,這個公式銜接我還不會,所以,··· 加不上。

           質因數分解那裏,有說倆數相乘就是指數相加。

            min(x,y) + max(x,y) = x + y ,  are you ok , 是不是有點感覺 ?

           故,GCD(n,m) * LCM(n,m) = ( 2^{min(n_{1},m_{1})} * 3^{min(n_{2},m_{2})} * 5^{min(n_{3},m_{3})} ) + ( 2^{max(n_{1},m_{1})} * 3^{max(n_{2},m_{2})} * 5^{max(n_{3},m_{3}) )

                 就等於 n * m        ,         證畢 。

   LCM(n,m) = \frac{n * m}{GCD(n,m)} ,   程序寫法 : lcm = n * m / gcd(n,m);

   程序裏面,您可不能這樣寫,因爲 n * m 較大時, 可會爆 long long 。想一想,要改成什麼樣呢 ???

          答 :  lcm = \frac{n}{gcd(n,m)} * m,  程序寫法 : lcm = n / gcd(n,m) * m;

求GCD我知道的實現方法:

  • 暴力枚舉、
  • 更相減損、
  • 輾轉相除(也叫歐幾里得)
  • 二進制算法(更相減損+輾轉相除優化)、
  • 貝祖等式優化歐幾里得(輾轉相除)、
  • 硬件+奇偶優化

代碼更新ing...

 

威爾遜定理證明的一種方法:

對於奇素數,令 a \in A = \left \{ 2,3,\cdots ,p-2 \right \}, 則 B = \left \{ a, 2a, 3a, \cdots ,(p-1)a \right \} 中不會對於除數 p 同餘的倆個數; 事實上,若 \alpha ^{a} ,\beta a \in B\alpha ^{a} \equiv \beta a(mod~p),則 a \left | a - \beta \right | 可被 p 除盡,而 \left | a-\beta \right | a\in B,但 B 中數不可能被 p 除盡。    於是 B 中數被 p 除得到的餘數形成的集合 C = {1, 2, ··· , p-1}。

       設 B 中被 p 除餘 1 的數 是 \gamma a:

  1.    若 \gamma = 1,則 \gamma = a, \gamma a 被 p 除於 a ,又 a \geqslant 2,與 \gamma a \equiv 1(mod ~ p) 矛盾,故 \gamma \neq 1
  2.    若 \gamma = p - 1,則 \gamma = pa - a,ta 被 p 除餘 a,所以 \gamma \neq p - 1。
  3.    若 \gamma = a,則 \gamma a = a^{2},由於 a^{2} \equiv 1 (mod~p) ,故應有 a^{2} - 1 = (a+1) * (a-1) \equiv 0 (mod ~p) ,  這隻能是 a = 1 或 a = p-1,此與 a \in A 矛盾,故 \gamma \neq a

    由 1、2、3 得,\gamma \neq a,且 \gamma \in A

    a 不同時,\gamma 亦相異; 若 a_{1} \neq a_{2}~, ~a_{1},a_{2}\in A,  且 \gamma a_{1} \equiv \gamma a_{2} \equiv 1~(mod~p),因 \gamma a_{1},\gamma a_{2} \in B,  而 B 中數關於 mod p 不同餘,可見 a_{1} \neq a_{2} ,  則 \gamma_{1} \neq \gamma_{2} 。

   依次取 a 爲 2, 3, ···, \frac{p-1}{2}; 使 \gamma a \equiv 1~(mod~p) 的數 \gamma 分別爲 \frac{p-1}{2}+1,~\frac{p-1}{2}+2, ~\cdots , p - 2,   即:>

        2*(\frac{p-1}{2}+1) \equiv 3*(\frac{p-1}{2}+2) \equiv \cdots \equiv \frac{p-1}{2}~(p-2) \equiv 1~(mod~p)

從而

        [2*(\frac{p-1}{2}+1)]~[3*(\frac{p-1}{2}+2)]~\cdots ~[\frac{p-1}{2}~(p-2)]\equiv 1~(mod p)

                      2 * 3 * ··· (p-2)  \equiv  1 (mod p)

又    (p-1)!  \equiv  -1 (mod p),則 

        (p-1)! = 1 * 2 * 3 * ··· * (p-2) * (p-1)  \equiv  -1 (mod p)

從而 (p-1)! + 1 可被 p 除盡。

        若 p 是合數, p 有因數 q, 1< q\leqslant p-1, 從而 (p-1)! 可被 q 整除;(p-1)! + 1 不能被 q 整除,亦不能被 p 整除。


計算機RSA算法部分數論知識

 Miller_Rabin素數判定 :有一定概率會失敗,我們稱這樣判斷的素數爲 "工業素數",我在加密博客RSA算法判斷素數時便採用的  Miller_Rabin素數判定。

數論學家利用費馬小定理研究出許多種素數測試方法,Miller_Rabin素數測試算法是其中一種,其過程如下:

  1. 計算奇數 M,使得 N = 2^{r} * M + 1
  2. 選擇一個隨機數 A < N
  3. 對於任意 i < r,若 A^{2^{i}*~M} mod ~ N = N - 1,則 N 通過隨機數 A 的測試
  4. 或者,若 A^{M} mod ~ N = 1,則 N 通過隨機數 A 的測試
  5. 讓 A 取不同的值對 N 進行 5 次測試,若全部通過則判定 N 爲素數

若 N 通過一次測試,則 N 不是素數的概率爲 25% ,若 N 通過 t 次測試,則 N 不是素數的概率爲 \frac{1}{4^{t}}。事實上,t = 5 時,N 不是素數的概率爲 \frac{1}{128},N 爲素數的概率已經大於 99.99%。實際應用中,可首先用 300 ~ 500 個小素數對 N 進行測試,以提高Miller_Rabin素數測試通過的概率,從而提高測試速度。而在生成隨機素數時,選取的隨機數最好讓 r = 0,則可省去 步驟3 的測試,進一步提高測試速度。 

#include <stdio.h>
#include <time.h>
#include <stdlib.h>
const int cnt = 10;

int modular_exp( int a, int m, int n )    // 模冪運算採用平方 -- 乘降冪法算法(下面的模運算細說),RSA 加密重點, 可以改成迭代
{
	if( 0 == m )    return 1;
	if( 1 == m )    return ( a % n );
	long long w = modular_exp( a, m>>1, n );
	w = w * w % n;
	if( m & 1 )     w = w * a % n;
	return w;
}
/* 平方 -- 乘降冪法算法: 避免出現 大數的中間值,比如倆個 200 位素數相乘變成 40000 位的中間值 */

bool Miller_Rabin( int n )
{
	if( 2 == n )    return true;
	for( int i = 0; i < cnt; i ++ )
	{
	    int a = rand( ) % ( n - 2 ) + 2;
	    if( modular_exp( a, n, n ) != a )    return false;
	}
	return true;
}

int main(void)
{
	srand( time(NULL) );
	int n;
	scanf("%d",&n);
	if( Miller_Rabin(n) ) 
	    printf("%s","Probably a prime\n");  // 質數
    else
        printf("%s","A composite\n");  // 合數
	return 0;
}

假如隨機數選取 4 個數是 2、3、5、7,則在 2.5 *10^{13} 以內唯一一個判斷失誤的數爲 3 215 031 751。


費馬定理 :  若 p 爲素數,a 爲正整數,且 a 和 p 互質,則 : a^{p-1}\equiv 1(mod~p)

證明 :

首先,p-1 個整數 a, 2a, 3a, ··· (p-1)a 中沒有一個是 p 的倍數。

其次,a, 2a, 3a, ···(p-1)a 中沒有任何倆個同餘於模 p 的。

於是 :  a, 2a, 3a, ···(p-1)a 對模 p 的同餘既不是0,也沒有倆個同餘相同,因此,這 p-1 個數對模 p 的同餘一定是 1, 2, 3, ···p-1 的某一種排列,即: 

a * 2a *3a* \cdots *(p-1)a \equiv 1 *2 * 3 * \cdots *(p-1)(mod~p)

化簡爲 :

 a^{p-1} * (p-1)!~\equiv ~(p-1)!~(mod~p)

又由於 p 是素數,根據威爾遜定理得出 (p-1)! 和 p 互質。所以約去 (p-1)!,得到 :

a^{p-1} \equiv 1(mod~p)

其實這是一種特殊形式,一般情況下,若 p 爲素數,則 : a^{p} \equiv a(mod~p),這就是著名的費馬小定理。


歐拉定理 :  若 a 與 m 互質,則 a^{\phi(m)} \equiv 1~(mod~m)

        費馬定理是用來闡述在素數模下,指數的同餘性質。當模是合數的時候,就要應用範圍更廣的歐拉定理了。

                   歐拉函數 : 對正整數 n,歐拉函數是小於等於 n 的數中與 n 互質的數的數目。

                   歐拉函數又稱爲 \phi 函數,如 \phi(8) = 4,因爲 1、3、5、7 均和 8 互質。

                   引理( 1 ):  

                             1.  如果 n 爲某一個素數 p,則 \phi(p) = p -1;

                             2.  如果 n 爲某一個素數 p 的冪次 p^{a},則 : \phi(p^{a}) = (p-1)*p^{a-1};

                             3.  如果 n 爲任意倆個互質的數 a、b 的積,則 : \phi(a*b) = \phi(a)*\phi(b) ;   

               證明:

                             1.  顯然;

                             2.  因爲比  p^{a} 小的正整數有 p^{a}-1 個。其中,所有能被 p 整除的那些數可以表示成   p*t~(t=1,2, \cdots p^{a-1}-1),即共有 p^{a-1}-1 個這樣的數能被 p 整除,從而不與  p^{a} 互質。所以,\phi(p^{a}) = p^{a}-1-(p^{a-1}-1)=(p-1)*p^{a-1};

                              3.  在比 a * b 小的 a * b - 1 個整數中,只有那些既與 a 互質(有 \phi(a) 個),又與 b 互質(有 \phi(b) 個)的數,纔會與 a * b 互質。顯然滿足這種條件的數有 \phi(a) * \phi(b) 個。所以 \phi(a*b)=\phi(a)*\phi(b)

           比如: 要求 \phi(40),因爲40 = 5 * 8,且  5 和 8 互質,所以: \phi(40) = \phi(5) * \phi(8) = 4*4=16。而 \phi(16) = (2-1)*2^{3} = 8

                     引理( 2 ):   

                                    設 n = p_{1}^{a1} * p_{2}^{a2}* p_{3}^{a3}*\cdots * p_{k}^{ak} 爲正整數 n 的素數冪乘積表達式,則:

                                    \phi(n) = n*(1-\frac{1}{p_{1}})*(1-\frac{1}{p_{2}})*\cdots *(1-\frac{1}{p_{k}})      

                      證明:  由於諸素數冪互相之間是互質的,根據引理(1)得出:

                                  \phi(n)=\phi(p_{1}^{a1})*\phi(p_{2}^{a2})*\cdots *\phi(p_{k}^{ak})

                                            =p_{1}^{a1}*p_{2}^{a2}*\cdots *p_{k}^{ak}*(1-\frac{1}{p_{1}})*(1-\frac{1}{p_{2}})*\cdots *(1-\frac{1}{p_{k}})

                                            =n*(1-\frac{1}{p_{1}})*(1-\frac{1}{p_{2}})*\cdots *(1-\frac{1}{p_{k}})

            比如:  \phi(100) = \phi(2^{2}*5^{2})=100*(1-\frac{1}{2})*(1-\frac{1}{5})=40

 

素數的一般判別方法:

  1. 枚舉 O(N^{2})  優化爲  O(N\sqrt N)
  2. 威爾遜逆定理  O(N)    基本沒用,需要大數計算來擴展
  3. Miller_Rabin素數判定  O(\log _{2}N * cnt),RSA加密術用 C/C++ 實現需要實現大數,讓倆個素數達到 1024 位以上
  4. Eratosthenes埃篩    O(N * \log _{2} \log _{2} N)  非常接近線性
  5. 歐拉篩(線性篩)  O(N) 
  6. 位圖篩法 時間複雜度同篩法,空間複雜度極小~
  7. 安利資料:判定素數

模 %

模 廣泛應用,

  •   時鐘、手錶是 模 12 系統;
  •   計算機的 n 位運算器是 模 n 系統,
  •   算盤 也是一個模系統;
  •   哈希算法本質也是模 運算,讓餘數保持在一個固定的範圍。
  •   我們約定的星期幾,其實也是一個模 7(6) 系統,
  •    Web 編程的分頁也是 模運算
  •    ... ...

加法模,例如時鐘,10 - 4 = 10 - (12 - 4) = 10 + 8 = 6 (mod 12),

               模  7 系統裏, 3 + 3 = 6 (mod 7), 6 + 6 = 5(mod 7),

乘法模,在模 13 系統裏, 11 * 9 = 8 (mod 13),

            11 * 9 = 99 % 13 = 8,

模的另外一種表現形式 : n % p  是否整除可以寫爲  \frac{n}{p} * p = n,還可以擴展代替除法

隨時取模(加法和乘法):

  •  (a+b)%p = (a%p + b%p) % p ,運用這個模性質,不會讓 程序裏的 a + b 溢出;
  •  (a*b)%p = [ (a%p) * (b%p) ] %p,基本同上;

利用上面倆個性質,可將模冪運算轉化成模乘運算如,以計算 a^{9} % n 爲例,可以分解爲( a^{8}% n × a % n)%n,將a^{8} % n又可以繼續分解爲(a^{4} % n × a^{4} % n)%n,a^{4} % n最終分解爲(a^{2} % n × a^{2} % n)%n ,a^{2} % n 即 (a % n * a % n)%n。


費馬大定理

          當 n \geqslant 3 時,x^{n}+y^{n}=z^{n} 方程不存在自然數解。

 


數論推薦資料:基礎數論模版大全\勾股數組

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章