1. 程式人生 > 其它 >擴充套件歐幾里德和求最小解以及例題青蛙的約會題解

擴充套件歐幾里德和求最小解以及例題青蛙的約會題解

技術標籤:好好學習

前幾天比賽的時候遇見一道擴充套件歐幾里德的題沒寫出來,於是後來就去把擴充套件歐幾里德學了一下。參考部落格是這個:點我
歐幾里德定理:gcd(a,b)=gcd(b,a%b),這是用來求最大公約數束的。用遞迴來寫程式碼是:

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

那麼擴充套件歐幾里德就是用ax+by=gcd(a,b)來求解x和y。這個應用也可以再推衍成ax+by=c→ax+by=cgcd(a,b)/gcd(a,b)→ax/(c/gcd(a,b))+by/(c/gcd(a,b))=gcd(a,b),那麼最後求出來的解x就等於x0

c/gcd,y=y0c/gcd。
再說回一開始的a
x+by=gcd(a,b),這個式子肯定不止一個解,那我們可以先求出一個通解,這個通解的式子是:
x0=y1,y0=x1-a/b
y1;下面是證明:
因為gcd(a,b)=gcd(b,a%b)
a*(x0)+b*(y0)=b*(x1)+(a%b)y1;
a*(x0)+b*(y0)=b*(x1)+(a-k*(b))y1;
a*(x0)+b*(y0)=b*(x1)+(a-(a/b)b)y1;
a*(x0)+b*(y0)=b*(x1)+a*(y1)-(a/b)b*(y1);
a*(x0-y1)+b*(y0-x1+(a/b)y1)=0;
所以x0=y1,y0=x1-a/b*y1,x1,y1又根據x2,y2求解,x2,y2又根據x3,y3求解,那麼什麼時候才會停下來?只有當滿足一開始的歐幾里德條件b=0
的時候,這個時候a就是最大公約數,也就是我們前面求出來的x0*(c/gcd),y=y0*(c/gcd)的gcd,並且x=1,y=0;以下是擴充套件歐幾里德的程式碼:

int ex_gcd(int a,int b,int &x,int &y){
    if(b==0){ //歐幾里德
        x=1;
        y=0;
        return a; //此時的a就是最大公約數gcd
    }
    int gcd=ex_gcd(b,a%b,x,y); //求出這個gcd用於求a*x+b*y=c的通解
    int temp=x;
    x=y;
    y=t-a/b*y;
    return
gcd; //返回gcd }

然後我們根據這個函式求出了ax+by=gcd(a,b)時候的通解之後,就要根據一開始的式子求ax+by=c的通解:x=xc/gcd,y=yc/gcd;這個時候我們就求出這個二元一次方程的通解了。
注意,這裡只是通解,或者說,這裡的x,和y只是這個方程的一組解,如果題目要求我們求出最小解的話我們還得繼續推:
求最小解肯定是希望形式變成:
a*(x-k)+b*(y-l)=c→ax-ak+by-bl=c;
然後我們令k=bd,l=ad,就得到ax-abd+by-bad=c→a*(x-bd)+b(x-ad)=c;這個時候式子就等於一開始的式子ax+by=c;現在我們要求這個d:
因為通解應該是整數,所以bd和ad也應該是整數,而且我們要求的是x和y的最小解,bd和ad越小,求出的最小解x和y才越接近原點,所以此時的d=1/gcd,就是a和b的最小公約數的倒數。
我們前面求出的通解x和y有可能大於0也有可能小於0,如果大於0,直接讓x-bd減到最接近原點也就是再減一個bd就小於0為止,那就是直接用x對b*d取模,求y也同理:x=x%(b/gcd),y=y%(a/gcd);
否則,就在x=x%(b/gcd),y=y%(a/gcd)的基礎上再加一個b/gcd和a/gcd,因為負數的時候我們取模也是求出在負半軸上最接近原點的數,只需要再加上一個被取模的數就能變成最小正解了:x=x%(b/gcd)+b/gcd,y=y%(a/gcd)+a/gcd

然後是例題青蛙的約會,這是原題地址
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

這道題是典型的擴充套件歐幾里德,讓我們求二元一次方程的最小解,設跳的次數為d,跳了k圈,列出式子:x+md-y+nd=k*l→(m-n)d-kl=y-x;
然後根據剛剛講的擴充套件歐幾里德就可以做了

#include<iostream>
#include<string.h>
#include<string>
#include<algorithm>
#include<stdio.h>
#include<bits/c++io.h>
using namespace std;

long long ex_gcd(long long a,long long b,long long &d,long long &k){
    if(b==0){
        d=1;
        k=0;
        return a;
    }
    long long ans=ex_gcd(b,a%b,d,k);
    long long temp=d;
    d=k;
    k=temp-(a/b)*k;
  //  printf("k=%lld d=%lld ans=%lld\n",k,d,ans);
    return ans;
}

int main(int argc,char const*argv[]){
    long long x,y,m,n,l;
    scanf("%lld%lld%lld%lld%lld",&x,&y,&m,&n,&l);
    long long a=m-n,b=l,c=y-x;
    long long d,k;
    if(m==n&&x!=y) printf("Impossible");
    else{
        if(a<0){
        a=-a;
        c=-c;
        }
        long long ans=ex_gcd(a,b,d,k);
        if(c%ans==0){
            d=d*c/ans;
            if(d>0){
                d=d%(b/ans);
            }
            else{
                d=d%(b/ans)+b/ans;
            }
            printf("%lld",d);
        }
        else printf("Impossible");
    }
    return 0;
}