1. 程式人生 > >擴充套件歐幾里德(詳細,從推導到運用)

擴充套件歐幾里德(詳細,從推導到運用)

各位大佬,轉載必須註明一下部落格,自己寫的不容易。/流眼淚

一 引例:

求兩個數的gcd(a,b)=a和b兩個數的最大公倍數?

  • 短除法

  • 更相減損法

  • 輾轉相除法

1、短除法:

其實短除法的核心   唯一分解定理。我們要求的最大公約數,其實也是它本身的一部分因子。

複雜度 O( min\left \{ \sqrt{n} \right ,\sqrt{m}\}^{k}\ ,k=gcd(n,m) )

注意:K=gcd(n,m)這個數的因子個數。

我們演示一遍即可。

2、更相減損術:

這個方法是來自我國數學:《九章算術》可以求最大公約數。

複雜度:O(N)

3、輾轉相除法:(歐幾里德)

複雜度: log_{2}(min(n,m) )

影象 小部件

 貼上程式碼:以上三種方法的使用。

#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. 求解 不定方程

  2. 求解 同餘方程

  3. 求解 模的逆元

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 所代替。(這個就輾轉相除的核心)

每次輾轉的都是除數餘數交替相除的過程。

被除數變成除數:   {\color{Red} a_{2}=b_{1}}               

除數變成餘數:       {\color{Red} b_{2}=(a_{1}\%b_{1})}

所以在公式推導過程有:

{\color{Red} a_{1}x+b_{1}y=gcd(a_{1},b_{1}) \Leftrightarrow b_{1}x+(a_{1}\%b_{1})y=gcd(a_{1},b_{1})}

1.2.2、在求特解的過程中發現:

上面的式子可能有問題!!!

問題出現在哪裡呢,我們發現,其實a,b在變化的過程中,係數x,y也會對應發生變化。

我們推導一下它的變化過程吧!!!

{\color{Red} a_{1}x_{1}+b_{1}y_{1}=gcd(a_{1},b_{1}) \Leftrightarrow b_{1}x_{2}+(a_{1}\%b_{1})y_{2}=gcd(a_{1},b_{1})}

{\color{Red} gcd(a_{1},b_{1})=a_{1}x_{1}+b_{1}y_{1} =b_{1}x_{2}+(a_{1}\%b_{1})y_{2}}

{\color{Red} a_{1}x_{1}+b_{1}y_{1} =b_{1}x_{2}+(a_{1}\%b_{1})y_{2}}

上面的過程中,a和b都是相同的,我們可以去掉下標。

{\color{Red} ax_{1}+by_{1} =bx_{2}+(a\%b)y_{2}}

移入一個求餘數的公式:{\color{Red} a\%b=a-\left \lfloor \frac{a}{b} \right \rfloor*b}

代入得到:{\color{Red} ax_{1}+by_{1} =bx_{2}+(a-\left \lfloor \frac{a}{b} \right \rfloor*b)y_{2}}

因為我們需要得到x1=****x2         y1=*****y2;

這樣的關係。所以我們用到了待定係數法(聽著很炫,其實就是表示為   **a+**b的形式).

{\color{Red} ax_{1}+by_{1} =ay_{2}+b(x_{2}-\left \lfloor \frac{a}{b} \right \rfloor*y_{2})}

把對應a和b當作變數,把對應的係數相等起來得到:

  • {\color{Red} x_{1}=y_{2}}
  • {\color{Red} y_{1}=x_{2}-\left \lfloor \frac{a}{b} \right \rfloor*y_{2}}

我們現在推匯出來了的東西進行運用。

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;

我們剛才不是推導了嗎?

  • {\color{Red} x_{1}=y_{2}}
  • {\color{Red} y_{1}=x_{2}-\left \lfloor \frac{a}{b} \right \rfloor*y_{2}}

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)倍數關係。

所以我們求解的過程可以彎曲一下。

已知:

{\color{Red} ax_{0}+by_{0}=gcd(a,b)}

湊一下:

{\color{Red} ax_{1}+by_{1}=c}

{\color{Red} ax_{1}+by_{1}=gcd(a,b)*\frac{c}{gcd(a,b)}}

再看看已知:我們就可以得出一組特解:

{\color{Red} x_{1}=x_{0}*\frac{c}{gcd(a,b)}}

{\color{Red} y_{1}=y_{0}*\frac{c}{gcd(a,b)}}

得出:

{\color{Red} ax_{1}+by_{1}=c}

1.3.2、求多組整數解:

我們其實知道這樣的二元一次方程組,可以有很多解。

那麼我們怎麼做才行呢????

一個字就是:   湊!!!!!!

我們想一下,其他解是怎樣的形式!!!

{\color{Red} a(x_{1}+???)+b(y_{1}+???)=c}      其中(x1,y1是上面)

為了保持左邊的值等於右邊,我們湊一下出來了;

???滿足:在a*???+b*???=0;

那麼我們用gcd(a,b)作分母,只要分子有另一方的倍數即可。

所以有:

{\color{Red} a(x_{1}\pm \frac{k*b}{gcd(a,b)})+b(y_{1}\mp \frac{k*a}{gcd(a,b)})=c}

一個詞總結再好不過:彼消此長

x=x_{1}\pm\frac{k*b}{gcd(a,b))}

y=y_{1}\mp\frac{k*a}{gcd(a,b))}

1.3.3、求最小正整數解   :(x  or  y)

通過上面的此消彼長.

只要我們讓x的不斷減少,當減少到負數,那麼上一次的x的必定是最小正整數解。

其實我們可以用for迴圈歷遍。

不妨設:

t=\frac{b}{gcd(a,b)}

只要滿足:

x_{1}-k*t>=0\ \ \&\& \ x_{1}-(k+1)*t<0

後來發現這個最小正整數解不就是:

x=x_{1}\%t

 但是注意細節:(x1可能為負數)

x=(x_{1}\%t+t)\%t

還要注意:(a,b,c可能為負數,使得  t 為負數)

t=abs(t),\ \ \ \ x=(x_{1}\%t+t)\%t

到時候還會補充逆元,同餘方程等數論知識!!!!