擴充套件歐幾里德和求最小解以及例題青蛙的約會題解
技術標籤:好好學習
前幾天比賽的時候遇見一道擴充套件歐幾里德的題沒寫出來,於是後來就去把擴充套件歐幾里德學了一下。參考部落格是這個:點我
歐幾里德定理: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
再說回一開始的ax+by=gcd(a,b),這個式子肯定不止一個解,那我們可以先求出一個通解,這個通解的式子是:
x0=y1,y0=x1-a/by1;下面是證明:
因為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
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;
}