數論專題(一)數論基本概念


轉載自:https://blog.csdn.net/whereisherofrom/article/details/78922798

素數:http://www.cnblogs.com/fzl194/p/8810399.html

快速冪:http://www.cnblogs.com/fzl194/p/8807138.html

一、數論基本概念

      1、整除性
      2、素數
           a.素數與合數
           b.素數判定
           c.素數定理
           d.素數篩選法
      3、因數分解
           a.算術基本定理
           b.素數拆分
           c.因子個數
           d.因子和
      4、最大公約數(GCD)和最小公倍數(LCM)
      5、同余
           a.模運算
           b.快速冪取模
           c.循環節
一、數論基本概念
      1、整除性
      若a和b都為整數,a整除b是指b是a的倍數,a是b的約數(因數、因子),記為a|b。整除的大部分性質都是顯而易見的,為了闡述方便,我給這些性質都隨便起了個名字。
      i)  任意性,若a|b,則對於任意非零整數m,有am|bm。
      ii) 傳遞性,若a|b,且b|c,則a|c。
      iii) 可消性,若a|bc,且a和c互素(互素的概念下文會講到),則a|b。
      iv) 組合性,若c|a,且c|b,則對於任意整數m、n,有c|(ma+nb)。
      拿一個我還未出生時的初二數學競賽題就能概括整除的性質了。
     【例題1】(公元1987年初二數學競賽題) x,y,z均為整數,若11|(7x+2y-5z),求證:11|(3x-7y+12z)。 
      非常典型的一個問題,為了描述方便,令a = (7x+2y-5z),b = (3x-7y+12z),通過構造可以得到一個等式:4a + 3b = 11(3x-2y+3z),則3b = 11(3x-2y+3z) - 4a。
      任意性+組合性,得出 11 |(11(3x-2y+3z) - 4a) = 11|3b。
      可消性,由於11和3互素,得出 11 | b,證明完畢。
      2、素數
      a.素數與合數
      素數又稱質數,素數首先滿足條件是要大於等於2,並且除了1和它本身外,不能被其它任何自然數整除;其它的數稱為合數;而1既非素數也非合數。
      b.素數判定
      如何判定一個數是否為素數?
      i)  對n做[2, n)范圍內的余數判定(C++中的'%'運算符),如果有至少一個數用n取余后為0,則表明n為合數;如果所有數都不能整除n,則n為素數,算法復雜度O(n)。
      ii) 假設一個數能整除n,即a|n,那么n/a也必定能整除n,不妨設a <= n/a,則有a^2 <= n,即a <= sqrt(n)(sqrt表示對n開根號),所以在用i)的方法進行取余的時候,范圍可以縮小到sqrt(n),所以算法復雜度降為O( sqrt(n) )。
      iii) 如果n是合數,那么它必然有一個小於等於sqrt(n)的素因子,只需要對sqrt(n)內的素數進行測試即可,需要預處理求出sqrt(n)中的素數,假設該范圍內素數的個數為s,那么復雜度降為O(s)。
      c.素數定理
      當x很大時,小於x的素數的個數近似等於x/ln(x),其中ln(x)表示x的自然對數,用極限表示如圖一-2-1所示:
圖一-2-1
      從這個定理可以發現,程序中進行素數判定的時候,用ii)方法和iii)方法差了至少一個數量級。
      d.素數篩選法
     【例題2】給定n(n < 10000)個數,范圍為[1, 2^32),判定它是素數還是合數。
      首先1不是素數,如果n>1,則枚舉[1,sqrt(n)]范圍內的素數進行試除,如果至少有一個素數能夠整除n,則表明n是合數,否則n是素數。
      [1,sqrt(n)]范圍內的素數可以通過篩選法預先篩出來,用一個數組notprime[i]標記i是素數與否,篩選法有很多,這里介紹一種最常用的篩選法——Eratosthenes篩選法。
      直接給出偽代碼:
   #define MAXP 65536
   
#define LL __int64

    void Eratosthenes() {
         notprime[1= true;
         primes[0= 0;
         for(int i = 2; i < MAXP; i++) {
             if!notprime[i] ) {
                  primes[ ++primes[0] ] = i;
                  //需要注意i*i超出整型后變成負數的問題,所以轉化成 __int64
                  for(LL j = (LL)i*i; j < MAXP; j += i) {

                       notprime[j] = true;
                  }
             }
         }
     }
      notprime[i]為真表明i為合數,否則i為素數(因為全局變量初始值為false,篩選法預處理只做一次,所以不需要初始化)。算法的核心就是不斷將notprime[i]標記為true的過程,首先從小到大進行枚舉,遇到notprime[i]為假的,表明i是素數,將i保存到數組primes中,然后將i的倍數都標記為合數,由於i*2、i*3、i*(i-1)在[1, i)的篩選過程中必定已經被標記為合數了,所以i的倍數只需要從i*i開始即可,避免不必要的時間開銷。
      雖然這個算法有兩個嵌套的輪詢,但是第二個輪詢只有在i是素數的時候才會執行,而且隨着i的增大,它的倍數會越來越少,所以整個算法的時間復雜度並不是O(n^2),而且遠遠小於O(n^2),在notprime進行賦值的時候加入一個計數器count,計數器的值就是該程序的總執行次數,對MAXP進行不同的值測試發現 int(count / MAXP) 的值隨着MAXP的增長變化非常小,總是維持在2左右,所以這個算法的復雜度可以近似看成是O(n),更加確切的可以說是O(nC),其中C為常數,C一般取2。
      事實上,實際應用中由於空間的限制(空間復雜度為O(n)),MAXP的值並不會取的很大,10^7基本已經算是極限了,再大的素數測試就需要用到Rabin-Miller
(第三章中會介紹該算法的具體實現)大數判素了。
      3、因數分解
      a、算術基本定理
      算術基本定理可以描述為:對於每個整數n,都可以唯一分解成素數的乘積,如圖一-3-1所示:
圖一-3-1
      這里的素數並不要求是不一樣的,所以可以將相同的素數進行合並,采用素數冪的乘積進行表示,如圖一-3-2所示:
圖一-3-2
      證明方法采用數學歸納法,此處略去。
      b、素數拆分
      給定一個數n,如何將它拆分成素數的乘積呢?
      還是用到上面講到的試除法,假設 n = pm 並且 m>1,其中p為素數,如果p > sqrt(n),那么根據算數基本定理,m中必定存在一個小於等於sqrt(n)的素數,所以我們不妨設p <= sqrt(n)。
      然后通過枚舉[2, sqrt(n)]的素數,如果能夠找到一個素數p,使得n mod p == 0(mod 表示取余數、也稱為模)。於是m = n/p,這時還需要注意一點,因為m中可能也有p這個素因子,所以如果p|m,需要繼續試除,令m' = m/p,直到將所有的素因子p除盡,統計除的次數e,於是我們得到了 n = (p^e) * n',然后繼續枚舉素數對n'做同樣的試除。
      枚舉完[2, sqrt(n)]的素數后,得到表達式如圖一-3-3所示:
圖一-3-3
      這時有兩種情況:
      i)  S == 1,則素數分解完畢;
      ii) S > 1, 根據算術基本定理,S 必定為素數,而且是大於sqrt(n)的素數,並且最多只有1個,這種情況同樣適用於n本身就是素數的情況,這時n = S。
      這樣的分解方式稱為因數分解,各個素因子可以用一個二元的結構體來存儲。算法時間復雜度為O( s ),s為sqrt(n)內素數的個數。
      c、因子個數
      朴素的求因子個數的方法為枚舉[1, n]的數進行余數判定,復雜度為O(n),這里加入一個小優化,如果m為n的因子,那么必然n/m也為n的因子,不妨設m <= n/m,則有m <= sqrt(n),所以只要枚舉從[1, sqrt(n)]的因子然后計數即可,復雜度變為O(sqrt(n))。
     【例題3】給定X,Y(X, Y < 2^31),求X^Y的因子數 mod 10007。
      由於這里的X^Y已經是天文數字,利用上述的枚舉法已經無法滿足要求,所以我們需要換個思路。考慮到任何整數都能表示成素數的乘積,那么X^Y也不例外,我們首先將X進行因數分解,那么X^Y可以表示成圖一-3-4所示的形式:
圖一-3-4
      容易發現X^Y的因子一定是p1、p2、...、pk的組合,並且p1可以取的個數為[0, Ye1],p2可以取的個數為[0, Ye2],pk可以取的個數為[0, Yek],所以根據乘法原理,總的因子個數就是這些指數+1的連乘,即(1 + Ye1) * (1 + Ye2) * ... * (1 + Yek)。
      通過這個問題,可以得到更加一般的求因子個數的公式,如果用ei表示X分解素因子之后的指數,那么X的因子個數就是(1 + e1) * (1 + e2) * ... * (1 + ek)。
      d、因子和
     【例題4】給定X,Y(X, Y < 2^31),求X^Y的所有因子之和 mod 10007。
      同樣還是將X^Y表示成圖一-3-4的形式,然后就變成了標准素數分解后的數的因子和問題了。考慮數n,令n的因子和為s(n),對n進行素數分解后的,假設最小素數為p,素因子p的個數為e,那么n = (p^e)n'。
      容易得知當n的因子中p的個數為0時,因子之和為s(n')。更加一般地,當n的因子中p的個數為k的時候,因子之和為(p^k)*s(n'),所以n的所有因子之和就可以表示成:
      s(n) = (1 + p^1 + p^2 + ... p^e) * s(n') = (p^(e+1) - 1) / (p-1) * s(n')
      s(n')可以通過相同方法遞歸計算。最后可以表示成一系列等比數列和的乘積。
      令g(p, e) = (p^(e+1) - 1) / (p-1),則s(n) = g(p1, e1) * g(p2, e2) * ... * g(pk, ek)。
      4、最大公約數(GCD)和最小公倍數(LCM)
      兩個數a和b的最大公約數(Greatest Common Divisor)是指同時整除a和b的最大因數,記為gcd(a, b)。特殊的,當gcd(a, b) = 1,我們稱a和b互素(上文談到整除的時候略有提及)。
      兩個數a和b的最小公倍數(Leatest Common Multiple)是指同時被a和b整除的最小倍數,記為lcm(a, b)。特殊的,當a和b互素時,lcm(a, b) = ab。
      gcd是基礎數論中非常重要的概念,求解gcd一般采用輾轉相除法(這個方法會在第二章開頭着重介紹,這里先引出概念),而求lcm需要先求gcd,然后通過lcm(a, b) = ab / gcd(a, b)求解。
      這里無意中引出了一個恆等式:lcm(a, b) * gcd(a, b) = ab。這個等式可以通過算術基本定理進行證明,證明過程可以通過圖一-4-1秒懂。
圖一-4-1
      需要說明的是這里的a和b的分解式中的指數是可以為0的,也就是說p1是a和b中某一個數的最小素因子,p2是次小的素因子。lcm(a, b)和gcd(a, b)相乘,相當於等式右邊的每個素因子的指數相加,即min{xi, yi} + max{xi, yi} = xi + yi,正好對應了a和b的第i個素數分量的指數之和,得證。
      給這樣的gcd和lcm表示法冠個名以便后續使用——指數最值表示法。
     【例題5】三個未知數x, y, z,它們的gcd為G,lcm為L,G和L已知,求(x, y, z)三元組的個數。
      三個數的gcd可以參照兩個數gcd的指數最值表示法,只不過每個素因子的指數上是三個數的最值(即min{x1, y1, z1}),那么這個問題首先要做的就是將G和L分別進行素因子分解,然后輪詢L的每個素因子,對於每個素因子單獨處理。
      假設素因子為p,L分解式中p的指數為l,G分解式中p的指數為g,那么顯然l < g時不可能存在滿足條件的三元組,所以只需要討論l >= g的情況,對於單個p因子,問題轉化成了求三個數x1, y1, z1,滿足min{x1, y1, z1} = g且max{x1, y1, z1} = l,更加通俗的意思就是三個數中最小的數是g,最大的數是l,另一個數在[g, l]范圍內,這是一個排列組合問題,三元組{x1, y1, z1}的種類數當l == g時只有1中,否則答案就是 6(l - g)。
       最后根據乘法原理將每個素因子對應的種類數相乘就是最后的答案了。
 
      5、同余
      a、模運算
      給定一個正整數p,任意一個整數n,一定存在等式n = kp + r; 其中k、r是整數,且滿足0 <= r < p,稱k為n除以p的商, r為n除以p的余數,表示成n % p = r (這里采用C++語法,%表示取模運算)。
      對於正整數和整數a, b, 定義如下運算:
      取模運算:a % p(a mod p),表示a除以p的余數。
      模p加法:(a + b) % p = (a%p + b%p) % p
      模p減法:(a  - b) % p = (a%p  - b%p) % p
      模p乘法:(a  * b) % p = ((a % p)*(b % p)) % p
      冪模p   : (a^b) % p = ((a % p)^b) % p
      模運算滿足結合律、交換律和分配律。
      a≡b (mod n) 表示a和b模n同余,即a和b除以n的余數相等。
     【例題6】一個n位十進制數(n <= 1000000)必定包含1、2、3、4四個數字,現在將它順序重排,求給出一種方案,使得重排后的數是7的倍數。
      取出1、2、3、4后,將剩下的數字隨便排列得到一個數a,令剩下的四個數字排列出來的數為b,那么就是要找到一種方案使得(a*10000 + b) % 7等於0。
      但是a真的可以隨便排嗎?也就是說如果無論a等於多少,都能找到這樣的b滿足等式成立,那么a就可以隨便排。
      我們將等式簡化:
      (a*10000 + b) % 7 = (a*10000%7 + b%7) % 7
      令 k = a*10000%7 = a*4%7,容易發現k的取值為[0, 7),如果b%7的取值也是[0, 7),那這個問題就可以完美解決了,很幸運的是,的確可以構造出7個這樣的b。具體參見下圖:
圖一-5-1
      b、快速冪取模
      冪取模常常用在RSA加密算法的加密和解密過程中,是指給定整數a,正整數n,以及非零整數p,求a^n % p。利用模p乘法,這個問題可以遞歸求解,即令f(n) = a^n%p,那么f(n-1) = a^(n-1)%p,f(n) = a*f(n-1) % p,這樣就轉化成了遞歸式。但是遞歸求解的時間復雜度為O(n),往往當n很大的時候就很難在規定時間內出解了。
      當n為偶數時,我們可以將a^n%p拆成兩部分,令b = a^(n/2)%p,則a^n%p = b*b%p;
      當n為奇數時,可以拆成三部分,令b = a^(n/2)%p,則a^n%p = a*b*b%p;
      上述兩個等式中的b可以通過遞歸計算,由於每次都是除2,所以時間復雜度是O(logn)。
      c、循環節
     【例題7】f[1] = a, f[2] = b, f[3] = c, 當n>3時 f[n] = (A*f[n-1] + B*f[n-2] + C*f[n-3]) % 53,給定a, b, c, A, B, C,求f[n] (n < 2^31)。
      由於n非常大,循環模擬求解肯定是不現實的,仔細觀察可以發現當n>3時,f[n]的值域為[0, 53),並且連續三個數f[n-1]、f[n-2]、f[n-3]一旦確定,那么f[n]也就確定了,而f[n-1]、f[n-2]、f[n-3]這三個數的組合數為53*53*53種情況,那么對於一個下標k<n,假設f[k]已經求出,並且滿足f[k-1] == f[n-1]且f[k-2] == f[n-2]且f[k-3] == f[n-3],  則f[n]必定等於f[k],這里的f[k...n-1]就被稱為這個數列的循環節。
       並且在53*53*53次計算之內必定能夠找到循環節,這個是顯而易見的。
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM