題目描述:
小Hi最近在追求一名學數學的女生小Z。小Z其實是想拒絕他的,但是找不到好的說辭,於是提出了這樣的要求:對於給定的兩個正整數N和M,小Hi隨機選取一個N的約數N',小Z隨機選取一個M的約數M',如果N'和M'相等,她就答應小Hi。
小Z讓小Hi去編寫這個隨機程序,到時候她review過沒有問題了就可以抽籤了。但是小Hi寫着寫着,卻越來越覺得機會渺茫。那麼問題來了,小Hi能夠追到小Z的機率是多少呢?
解決思想:
假設N有Nnum個約數,M有Mnum個約數,N和M共有same個相同的約數。對於一個公共約數,在N中抽取到它的概率爲1/Nnum,M爲1/Mnum,所以同時抽取到這個公共約數的概率爲1/(Nnum*Mnum)。一共有same個公共約數,則總概率爲same個1/(Nnum*Mnum)相加,則概率爲same/(Nnum*Mnum)。
如何求解same:same即爲N和M最大公因數的約數個數。
需要實現的算法:
1.給定一個數,求解它的約數總個數。
2.給定兩個數,求解它們的最大公因數。
實現算法原理:
1.O(sqrt(N))求約數個數法
比如要求解36的約數,我們可以把36如下分解:
36=1*36
36=2*18
36=3*12
36=4*9
36=6*6
注意我們只寫出了小於等於sqrt(36)個等式就找到了它(36)所有的約數,共九個。因爲當我們找到A的約數B時,也可默認得到A/B是A的另一個約數(如果A/B和B 不相等的話),這樣我們就想到,在計算N的約數個數時可以只檢查前sqrt(N)個數是否爲N的約數,若爲,則約數個數加2(若約數的平方等於N時再-1)
具體實現代碼如下:
long divisor_num(long num){
long i;
long result=0;
for(i=1;i<=sqrt(num);i++){
if(!(num%i)){
result+=2;
if(i*i==num)
result--;
}
}
return result;
}
2.使用Euclidean algorithm求解最大公因數
假設求解a、b的公因數gcd,且a>=b。易知,gcd也是a、a-b的公因數,那麼如何證明gcd是最大公因數呢,我是這樣想的。
因爲gcd是a、b的最大公因數,所以a=m*gcd,b=n*gcd,m和n互質(即m和n的最大公約數爲1,gcd已經完全從m、n抽取了相同的約數)。
兩式相減得到a-b=(m-n)*gcd,若m-n和m互質,那麼gcd也就是a與a-b的最大公約數。由於m和n互質,那麼顯然m-n和m互質。
由於gcd是a、a-b的最大公因數,那麼顯然gcd也是a、a%b(即a-kb)的最大公因數。
具體實現代碼如下:
long find_gcd(long x,long y){
return y==0?x:find_gcd(y,x%y);
}
當x<y時,迭代下一步就會調換xy的位置。
當每一次計算的餘數都會減小,所以最後y一定等於0從而算出最大公約數x。
算法整體代碼:
#include <iostream>
#include <cmath>
using namespace std;
long divisor_num(long num){
long i;
long result=0;
for(i=1;i<=sqrt(num);i++){
if(!(num%i)){
result+=2;
if(i*i==num)
result--;
}
}
return result;
}
long find_gcd(long x,long y){
return y==0?x:find_gcd(y,x%y);
}
int main()
{
long N,M;
cin>>N>>M;
long Nnum=divisor_num(N);
long Mnum=divisor_num(M);
long same=divisor_num(find_gcd(N,M));
long tmp=find_gcd(Nnum*Mnum,same);
cout<<Nnum*Mnum/tmp<<" "<<same/tmp;
return 0;
}