數論-輾轉相除法、唯一分解定理
沒有數學就沒有演算法;沒有好的數學基礎,也很難在演算法上有所成就。
數論被數學王子高斯譽為整個數學王國的皇后。數論是純粹數學的分支之一,主要研究整數的性質。從研究方法來看,數論大致可分為初等數論和高等數論。初等數論是用初等方法研究的數論,它的研究方法本質上說,就是利用整數環的整除性質,主要包括整除理論、同餘理論、連分數理論。高等數論則包括了更為深刻的數學研究工具。
我們在這裡主要學習初等數論。
首先看一下需要用的輔助巨集:
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可以唯一分解為有限個質數的乘積
這裡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);
}