1. 程式人生 > >數論-輾轉相除法、唯一分解定理

數論-輾轉相除法、唯一分解定理

沒有數學就沒有演算法;沒有好的數學基礎,也很難在演算法上有所成就。

數論被數學王子高斯譽為整個數學王國的皇后。數論是純粹數學的分支之一,主要研究整數的性質。從研究方法來看,數論大致可分為初等數論和高等數論。初等數論是用初等方法研究的數論,它的研究方法本質上說,就是利用整數環的整除性質,主要包括整除理論、同餘理論、連分數理論。高等數論則包括了更為深刻的數學研究工具。

我們在這裡主要學習初等數論。

首先看一下需要用的輔助巨集:

typedef pair<int, int> P;
const int maxn = 200 + 5;
const int INF  = 0x3f3f3f3f;  //極大值
const double pi = acos(-1);  // π

首先學習一下輾轉相處法,它也許是最廣為人知的數論演算法。

輸入:整數a,b

輸出:a,b的最大公約數,最小公倍數。

執行結果:

這個演算法是求最大公約數的,關鍵在於如下恆等式:

gcd(a,b)=gcd(b,a%b)/ 它和邊界條件gcd(a,0)=0一起構成了下面的程式、

int gcd(int a, int b)
{
    return (b == 0) ? a : gcd(b, a%b);
}

這個演算法稱為歐幾里得演算法。既然是遞迴,那麼會導致棧溢位麼?答案是不會。

可以證明,gcd函式的遞迴層數不超過4.785lgN + 1.6723 。其中N = max(a,b) .

利用gcd還可以求出兩個整數a和b的最小公倍數lcm(a,b)。這個結論很容易由唯一分解定理得到。設:

a = p1^e1 * p2^e2... * pr^er

b = p1^f1 * p2^f2... * pr^fr

則:

gcd(a,b) = p1^min(e1,f1) * p2^min(e2,f2)... * pr^min(er,fr)

lcm(a,b) = p1^max(e1,f1) * p2^max(e2,f2)... * pr^max(er, fr)

不難驗證gcd(a,b) * lcm(a,b) = a*b。 不過有了公式也不要大意,如果把lcm寫成a*b / gcd(a,b),可能會出現問題,a*b可能會溢位

正確的寫法是先除後乘,即a/gcd(a,b) * b。這樣可以保證最終結果在int範圍內,這個函式就不會出錯。而前一份程式碼卻不是這樣,即使最終答案在int範圍內,也有可能中間過程越界。

int lcm(int a, int b)
{
    return a / gcd(a, b) * b; //防止溢位:先除再乘!
}

最大公約數的性質:如果k > m! k與m! 互素當且僅當 k mod m! 與 m!互素。

最後忍不住吐槽一句:換部落格面板的時候竟然發現沒法點選換??還得修改程式碼才行??搞不懂為啥設計成這樣。

唯一分解定理:任何一個大於1的自然數N,如果N不為質數,那麼N可以唯一分解為有限個質數的乘積

N=P1^{a1}P2^{a2}P3^{a3}....Pn^{an}

這裡P1<P2<P3....<Pn均為質數。

void prime_factors(int n)
{
    vector<int> primes;
    int i, m;

    m = sqrt(n + 0.5); //減少運算量
    for(i = 2; i <= m; i++)
    {
        if(n % i == 0)
        {
            primes.push_back(i);
            while(n % i == 0)
                n /= i;
        }
        if(n == 1) //提前結束 節約時間
            break;
    }

    if(n > 1) //最後一個因數
        primes.push_back(n);
}