以POJ1061青蛙的約會爲例談擴展歐幾里得算法

青蛙的約會
Time Limit: 1000MS Memory Limit: 10000K
Total Submissions: 100498 Accepted: 19304
Description
兩 只青蛙在網上相識了,它們聊得很開心,於是覺得很有必要見一面。它們很高興地發現它們住在同一條緯度線上,於是它們約定各自朝西跳,直到碰面爲止。可是它 們出發之前忘記了一件很重要的事情,既沒有問清楚對方的特徵,也沒有約定見面的具體位置。不過青蛙們都是很樂觀的,它們覺得只要一直朝着某個方向跳下去, 總能碰到對方的。但是除非這兩隻青蛙在同一時間跳到同一點上,不然是永遠都不可能碰面的。爲了幫助這兩隻樂觀的青蛙,你被要求寫一個程序來判斷這兩隻青蛙 是否能夠碰面,會在什麼時候碰面。
我們把這兩隻青蛙分別叫做青蛙A和青蛙B,並且規定緯度線上東經0度處爲原點,由東往西爲正方向,單位長度1米,這樣我們就得到了一條首尾相接的 數軸。設青蛙A的出發點座標是x,青蛙B的出發點座標是y。青蛙A一次能跳m米,青蛙B一次能跳n米,兩隻青蛙跳一次所花費的時間相同。緯度線總長L米。 現在要你求出它們跳了幾次以後纔會碰面。
Input
輸入只包括一行5個整數x,y,m,n,L,其中x≠y < 2000000000,0 < m、n < 2000000000,0 < L < 2100000000。
Output
輸出碰面所需要的跳躍次數,如果永遠不可能碰面則輸出一行”Impossible”
Sample Input
1 2 3 4 5
Sample Output
4
Source
浙江

歐幾里得算法可以求出a,b的最大公約數。gcd(a,b)=gcd(b,a%b),公式兩邊b都靠近等號一側,因此很好記。

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

爲什麼歐幾里得算法是對的,記住一個式子:a=bq+r(假設a>b。若a小於b,gcd(a,b)=gcd(b,a)。因此a大於還是小於b無所謂)。因爲a%m==0且b%m==0,所以必有r%m==0:a和b的約數必是r(即a%b)的約數。同時若r%n==0,b%n==0,必有a%n==0,因此b和r的約數必是a的約數。因此a、b約數中最大值必是b、r約數中的最大值。

**

擴展歐幾里得算法exGcd(a,b,x,y):求a,b的最大公約數同時可以求得方程組ax+by=gcd的一組特殊解(x,y)。

**從式子ax+by=gcd可以看出必須先求出gcd,才能求出解。而遞歸中的卻如此。
1、假設已經找到gcd,那麼y=0,x=gcd,顯然是該方程的解。而且在每一層遞歸函數exGcd(a,b,x,y)裏都有一個解(x,y)。
2、在遞歸回溯過程中,假設在第n+1層exGcd(a(n+1),b(n+1),x,y)找到了(x0,y0)是方程的解。那麼有a(n+1)*x0+b(n+1)*y0=c(1式)。
3、根據遞歸過程,在第n層調用了exGcd(bn,an%bn,x,y)進入第n+1層。因此在第n+1層的解滿足a(n+1)=bn,b(n+1)=an%bn,即(1)式等價於:bn*x0+(an%bn)%y0=c(2式)。又因爲an%bn=an-bn*(an/bn)。所以(2式子)等價於bn*x0+(an-bn*(an/bn))*y0=bn(x0-(an/bn)*y0)+an*y0=c(3式)。
3、假設回溯到第n層時,解爲(x1,y1),an*x1+bn*y1=c(4式)。對比(3式)和(4式)發現x1=y0,y1=x0-y0*(an/bn)。所以第n層的解可以通過第n+1層的解遞推出來。在最深層有解x=gcd,y=0,因此層層回溯,每層都會有一個關於當前層exGcd(an,bn,x,y)的解(xn,yn)使得an*xn+bn*yn=gcd(an,bn)。因此回溯到第一層時就得到了關於第一層的解。
代碼如下:

long long exGcd(long long a, long long b, long long &x, long long &y){
    if(b==0){
        x=1;
        y=0;
        return a;
    }
    long long g=exGcd(b,a%b,x,y);
    long long temp=x;
    x=y;
    y=temp-(a/b)*y;//注意此處不能寫成y*a/b!
    return g;
}

擴展歐幾里得算法應用之一就是:求二元一次方程組的解(x,y), a*x+b*y=c。

注意,這裏右邊是c,不是gcd(a,b)。假設其中x是我們關心的,所以研究x的解的情況。計算步驟:
1、用擴展歐幾里得算法求解方程:a*x+b*y=gcd(a,b). 可以同時得到gcd以及 一組特解(x0,y0)
2、如果c%gcd!=0,那麼根據式子a*x+b*y=c可知其無整數解(因爲a,b都可以提一個公因數gcd,但是c不能提因子gcd,等號左右兩邊不等價)。
3、那麼a*x+b*y=c的x的特殊解x1等於a*x+b*y=gcd(a,b)的特殊解x0乘以c/gcd: x1=x0*c/gcd。4、這一步的構造很關鍵,因爲引入了整數t,t不同解不同,這就是爲什麼會有無數解。因爲x1*a+y1*b=x1*a+y1*b+(t*a*b/gcd-t*a*b/gcd)=a(x1+t*b/gcd)+b(y1-t*a/gcd)=c,所以方程a*x+b*y=c的x的通解就是x=x1+t*b/gcd=x0*c/gcd+t*b/gcd。

5、x有無數種情況,我們關心特殊情況,比如在實際問題中經常需要找大於0且最小的x的值。如何尋找?找x即找t,下面對t進行分析。 x=x0*c/gcd+t*b/gcd。這裏的x肯定大於0,減去t*b/gcd剩下的x0*c/gcd也大於0,所以直接對x=x0*c/gcd進行分析。不妨讓t=x/(b/gcd)得到x中有t個b/gcd,然後在x中減去這t個b/gcd:t=x/(b/gcd),x=x-t*(b/gcd)。此時得到的x可能小於等於0,因此要做判斷,x小於等於0時加上b/gcd即可。

首先,此題有兩個變量,跳的次數P和跳的圈數Q。需要求次數的最小值。當然聯想到拓展歐幾里得算法的應用:求解二元一次線性方程組。兩隻青蛙相遇的條件是:(x+mP) mod L =(y+nP) mod L=0,但是這樣寫不是方程式的形式,所以改成等價的方程的形式: (x+mP)-(y+nP) =Q L,等價於x-y+P(m-n)=QL,x-y=(n-m)P+QL。令c=x-y,a=n-m,b=L,則aP+bQ=c,這就是標準的二元一次方程組,兩個未知量求其中一個未知量(跳到次數P)的最小值。

#include <iostream>
#include <cstring>
#include<cstdio>
#include <algorithm>
using namespace std;
long long exGcd(long long  a,long long b,long long &x,long long &y){
    if(b==0){
        x=1;
        y=0;
        return a;
    }
    long long d=exGcd(b,a%b,x,y);
    long long tmpX=x;
    x=y;
    y=tmpX-y*(a/b);//不能寫成y*a/b
    return d;
}
int main()
{
    freopen("input.txt","r",stdin);
    freopen("output.txt","w",stdout);
    long long ansX,x,y,n,m,L,x0,y0,a,b,c,gcd;
    while(scanf("%lld%lld%lld%lld%lld",&x,&y,&m,&n,&L)==5){
        c=x-y;
        a=n-m;
        b=L;
        gcd=exGcd(a,b,x0,y0);
        if(c%gcd!=0) cout<<"Impossible"<<endl;
        else{
        ansX=x0*(c/gcd);
        long long t=ansX/(b/gcd);
        ansX=ansX-t*(b/gcd);
        if(ansX<0) ansX+=b/gcd;
        cout<<ansX<<endl;
        }

    }
    return 0;
}

歡迎留言,積極討論,一起進步!

發佈了77 篇原創文章 · 獲贊 22 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章