擴充套件歐幾里德(詳細,從推導到運用)
各位大佬,轉載必須註明一下部落格,自己寫的不容易。/流眼淚
一 引例:
求兩個數的gcd(a,b)=a和b兩個數的最大公倍數?
-
短除法
-
更相減損法
-
輾轉相除法
1、短除法:
其實短除法的核心 唯一分解定理。我們要求的最大公約數,其實也是它本身的一部分因子。
複雜度
注意:K=gcd(n,m)這個數的因子個數。
我們演示一遍即可。
2、更相減損術:
這個方法是來自我國數學:《九章算術》可以求最大公約數。
複雜度:O(N)
3、輾轉相除法:(歐幾里德)
複雜度:
影象 小部件
貼上程式碼:以上三種方法的使用。
#include<bits/stdc++.h> using namespace std; void DC(){ int a=888,b=664,gcd=1; while(1){ int flag=0; for(int i=2;i<=min(a,b);i++){ if(a%i==0&&b%i==0){ gcd*=i; a/=i,b/=i; flag=1; break; } } if(flag==0){ //兩者為互質 break; } } printf("最大公約數:gcd(664,888)=%d\n",gcd); printf("最小公倍數:lcm(664,888)=%d\n",gcd*a*b); } void XJ(){ //更相減損術 int a=888,b=664,temp=0,cnt=0; while(a-b!=0){ //printf("%d %d %d\n",a,b,temp); temp=a-b; a=b; b=temp; if(a<b) swap(a,b); } printf("%d\n",temp); } void GCD(){ //非遞迴寫法 int a=888,b=664,temp; while(a%b!=0){ temp=a%b; a=b; b=temp; } printf("%d\n",b); } int gcd(int a,int b){//遞迴寫法 return a%b==0?b:gcd(b,a%b); } int main() { DC(); XJ(); GCD(); cout<<gcd(664,888)<<endl; }
二 拓展歐幾里德:
上面提到了歐幾里德,大家都看出來了,僅僅處理了一個小問題,就是計算一個最大公約數。
但是,這個計算公約數速度非常快,可以達到log級別的都是優質演算法。
我們只是提及一下短除法和更相減損法,但是有時候面對小的數字,我們可以筆算一下這個最大公約數。
更多選擇第一第二種。我們以後用的就是log級別的gcd來處理問題。
我們介紹一下通常拓展歐幾里德給我們拓展了哪些問題。
-
求解 不定方程
-
求解 同餘方程
-
求解 模的逆元
1.1 求解不定方程 引例
計算2809和6731的最小公倍數和最大公約數,並將兩者的最大公約數表示成兩者的線性組合?
——《離散數學》·張小峰 P25 第4小題。
1.2 、解題過程:
模擬輾轉相除法:
6731=2809*2+1113
2809=1113*2+583
1113=583*1+530
583=530*1+53
530=53*10+0
上述過程就是輾轉相除法。求得53是最大公因數。
然後我們需要求出 6731 和 2890 的關係。所以我們需要求出他們和最大公因數53的關係。
我們通過上面的式子倒推回去,不斷表示兩者中小的數字。
求特解過程:
53= 53 - 0
= 583 - 530
= 583 -(1113 - 583)=583*2 - 1113
=(2809 - 1113*2)*2-1113=2809*2 - 1113*5
= 2809*2 -(6731 - 2809*2)*5
= 12 * 2809 - 5 * 6731
通過倒推的過程大家其實意識到了,就是用上面的式子來操作,就好比輾轉相除法的逆運算。
上面的推導過程就是拓展歐幾里德求解 最小解 x, y 的過程了。
1.2.1、在模擬輾轉相除法的過程中發現:
最開始的a和b:被 b 和 a%b 所代替。(這個就輾轉相除的核心)
每次輾轉的都是除數和餘數交替相除的過程。
被除數變成除數:
除數變成餘數:
所以在公式推導過程有:
1.2.2、在求特解的過程中發現:
上面的式子可能有問題!!!
問題出現在哪裡呢,我們發現,其實a,b在變化的過程中,係數x,y也會對應發生變化。
我們推導一下它的變化過程吧!!!
上面的過程中,a和b都是相同的,我們可以去掉下標。
移入一個求餘數的公式:
代入得到:
因為我們需要得到x1=****x2 y1=*****y2;
這樣的關係。所以我們用到了待定係數法(聽著很炫,其實就是表示為 **a+**b的形式).
把對應a和b當作變數,把對應的係數相等起來得到:
我們現在推匯出來了的東西進行運用。
1.2.3、再次回顧輾轉相除法:
我們通過這個輾轉相除法得到的就是一個公約數。
然後我們可以通過兩個數 a,b來推匯出一組關係來表示這個gcd(a,b)
求解其中的係數關係x,y。
下面式子關鍵是弄清楚輾轉相除法的核心進行推導:
1.2.4、關鍵思想:
不斷用上一個式子的除數和餘數來交替表示式子中的被除數 a 和除數 b
(你要是問我為什麼會這樣能弄出來最大公約數,我真不知道,那要問問歐幾里德) \逃
a x1 + b y1 = gcd(a,b)
b x2 + ( a % b ) y2 = gcd( b , a % b )
………………………………
第n次輾轉後——求到最大公約數時:
n *1 + 0 * 0 = gcd(a,b)
這個過程解釋清楚了吧!!!這裡挺難理解的,就是輾轉到最後,
突然發現沒有餘數了,那麼就說明這個 上一個式子的除數 n=gcd(a,b)
我們已經求出來了,並且我們能確保當前位置的 xn=1,yn=0;
我們剛才不是推導了嗎?
x1,y1 是上一個式子,x2,y2代表是下一個式子。
我們不就是進行回溯過去不斷用式子來表示 x1,y1。
最後回溯到最開始的 a,b不就是我們想要求出來的答案嗎!!!
1.2.5、程式碼表示過程:
#include<stdio.h>
int exgcd(int a,int b,int &x,int &y){//返回的是最大公約數.
//此時 a,b代表的是上一個式子的除數和餘數
if(b==0){//餘數為0
x=1;
y=0;
return a;
}
int r=exgcd(b,a%b,y,x);//遞迴求解gcd(a,b);
y=y-a/b*x;//不要在乎細節,這裡就是x,y的值調換,公式還是那樣.
return r; //別忘了這一句,不然得不到最大公約數
}
int main(){
int x,y,a=6731,b=2809;
int gcd=exgcd(a,b,x,y);
printf("%d = (%d)*%d + (%d)*%d",gcd,x,a,y,b);
}
1.2.6、執行結果:
1.3、再次回顧ax+by=c:
1.3.1、求特解:
上面說的,只能解決一個式子:那就是:ax+by=gcd(a,b)
大家思考一下,其實ax+by=c可能沒有解。
那就是 c%gcd(a,b)!=0
這就是我們解決這個式子的第一步!!!
第一步:
通過c%gcd(a,b)==0來判斷是否有通解。
第二步:
求解滿足ax+by=c的一組特解
若有通解的情況下,代入上面的程式碼我們解出來的一個x0,y0只是符合ax+by=gcd(a,b)
但 不是我們題目要求的ax+by=c的方程的根。
聰明如你是否想到了????
因為判斷通解的過程就說明這個c一定是gcd(a,b)倍數關係。
所以我們求解的過程可以彎曲一下。
已知:
湊一下:
再看看已知:我們就可以得出一組特解:
得出:
1.3.2、求多組整數解:
我們其實知道這樣的二元一次方程組,可以有很多解。
那麼我們怎麼做才行呢????
一個字就是: 湊!!!!!!
我們想一下,其他解是怎樣的形式!!!
其中(x1,y1是上面)
為了保持左邊的值等於右邊,我們湊一下出來了;
???滿足:在a*???+b*???=0;
那麼我們用gcd(a,b)作分母,只要分子有另一方的倍數即可。
所以有:
一個詞總結再好不過:彼消此長
1.3.3、求最小正整數解 :(x or y)
通過上面的此消彼長.
只要我們讓x的不斷減少,當減少到負數,那麼上一次的x的必定是最小正整數解。
其實我們可以用for迴圈歷遍。
不妨設:
只要滿足:
後來發現這個最小正整數解不就是:
但是注意細節:(x1可能為負數)
還要注意:(a,b,c可能為負數,使得 t 為負數)
到時候還會補充逆元,同餘方程等數論知識!!!!