題目:ios
先說歐幾里得算法:歐幾里得算法展轉相除求\(gcd\)。求\(a、b\)的\(gcd\),則利用的性質是:\(gcd(a,b)=gcd(b,a\%b)\),而\(gcd(a,0)=a\),這樣,展轉除下去,當第二個參數爲0,第一個參數就是最大公約數。算法
int gcd(int a,int b){ while(b!=0){ int tmp = a%b; a = b; b = tmp; } return a; }
擴展歐幾里得算法:擴展歐幾里得算法不只能夠用來求最大公約數,還能夠求逆元/知足ax+by=gcd(a,b)的x和y。基於的原理是:ax+by=gcd(a,b)必定存在解(x,y)。spa
一個用處就是:問ax+by=1是否有解,就是看a,b是否互質,即gcd(a,b)=1。.net
求知足ax+by=gcd(a,b)的(x,y)的過程,就是證實ax+by=gcd(a,b)必定有解的過程。code
\(ax+by=gcd(a,b)=gcd(b,a\%b)=bx_2+a\%by_2\)blog
又 \(a\%b=a-a/b*b\)遞歸
所以 \(ax+by =bx_2+(a-a/b*b)y_2 =ay_2+b(x_2-a/b*y2)\)get
從而 \(x = y2;y=x_2-a/by_2\)io
層層遞歸下去,最終當\(b=0\)時,返回\(gcd(a,b)=a\),此時有一組解\(x=1,y=0\),回溯,利用上式求出\(x,y\)。table
從而,這就創建了要求的(x,y)和前一狀態的關係,算法的遞歸實現以下:
int exgcd(int a,int b,int &x,int &y){ /*輸入ax+by=gcd(a,b)的a,b,返回gcd(a,b),同時求出知足此式的解(x,y)*/ if(b==0){ x = 1; y = 0; return a; } int p = exgcd(b,a%b,x,y); int tmp = x; x = y; y = tmp - a/b*y; return p; } int main(){ int x,y; int gcd = exgcd(24,18,x,y); printf("24*%d + 18*%d = %d\n",x,y,gcd); return 0; }
繼續.....
僅僅知道歐幾里得解\(ax+by=gcd(a,b)\)還不夠,擴展歐幾里得最好用的地方在於其求解乘法逆元。所謂逆元:若\(ax==1mod(m)\),則稱\(x\)是\(a\)關於\(m\)的逆元,逆元可能有許多個,求的是其中最小的一個。
\(ax==1mod(m)\) \(=>\) \(ax+my=1\)有解,求此式的解中最小的\(x\)便可。即求出此式\(x\),\(x=x\%m\),若是\(x<0\),\(x+=m\)。
求解代碼藉助exgcd:
/*求逆元模板*/ int exgcd(int a,int b,int &x,int &y){ if(b == 0){ x = 1; y = 0; return a; } int p =exgcd(b,a%b,x,y); int temp = x; x = y; y = temp - (a/b) *y; return p; } //求逆元 求a關於m的最小逆元 int cal(int a,int m){ int x,y; int gcd = exgcd(a,m,x,y); if(1%gcd != 0) return -1; x *= 1/gcd; int ans = x % m; if(ans<=0) ans+=m; //保證正的且最小 return ans; } /*另外一個簡潔版本的求逆元*/ void exgcd(int a,int b,int &x,int &y){ if(b == 0){ x = 1; y = 0; }else{ exgcd(b,a%b,y,x); y = y - (a/b)*x; //此處應注意 } }
例題:青蛙約會poj 1061
代碼解釋:https://blog.csdn.net/liangdong2014/article/details/38732745
poj 1061 青蛙約會 |
---|
兩隻青蛙在網上相識了,它們聊得很開心,因而以爲頗有必要見一面。它們很高興地發現它們住在同一條緯度線上,因而它們約定各自朝西跳,直到碰面爲止。但是它們出發以前忘記了一件很重要的事情,既沒有問清楚對方的特徵,也沒有約定見面的具體位置。不過青蛙們都是很樂觀的,它們以爲只要一直朝着某個方向跳下去,總能碰到對方的。可是除非這兩隻青蛙在同一時間跳到同一點上,否則是永遠都不可能碰面的。爲了幫助這兩隻樂觀的青蛙,你被要求寫一個程序來判斷這兩隻青蛙是否可以碰面,會在何時碰面。 咱們把這兩隻青蛙分別叫作青蛙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
思路:
在一個周長是\(L\)的環上,A青蛙處於\(x\)處,每次跳\(m\),B青蛙處於\(y\)處,每次跳\(n\),跳相同的步數後,兩青蛙處於同一位置。由此,設跳\(t\)步相遇,則:
\((x+m*t)\%L=(y+n*t)\%L\)
\(=>(m-n)*t\%L=(y-x)\%L\)
\(=>(m-n)*t\%L=y-x\)
若此式有解,則應有,
\(=>(m-n)*t_1+L*t_2=y-x\)有解,由exgcd知,有解的條件是
\(=>(y-x) \% gcd(m-n,L) =0\)
如有解,下面求解知足條件的最小\(t_1\),進而推出\(t\)。
由exgcd求出知足\((m-n)*t_1+L*t_2=gcd(m-n,L)\)的一組解:
exgcd(m-n,L,t1,t2);
因爲\(y-x\)是\(gcd(m-n,L)\)的整數倍,則\((m-n)*t_1+L*t_2=y-x\)的一個解是:
\(=>t_1=t_1*(y-x)/gcd(m-n,L)\);
注意這樣求出來的\(t_1\)可能並非最小,所以對\(t_1\)作以下操做:
\(=>t_1=t_1\%L\)變爲最小;
但\(t_1\)還多是負值,所以,變爲正的:
\(=>if_{ t_1<0}:t_1=t_1+L;\)
從而解出了\(t=t_1\)。
實現代碼:
#include<iostream> using namespace std; typedef long long ll; ll x,y,m,n,L; ll gcd(ll a,ll b){ while(b!=0){ ll tmp = a%b; a = b; b = tmp; } return a; } void exgcd(ll a,ll b,ll&x,ll &y){ if(b == 0){ x = 1; y = 0; }else{ exgcd(b,a%b,y,x); y = y - (a/b)*x; } } int main(){ ll a,b,c,d,t1,t2,L2; while(scanf("%lld%lld%lld%lld%lld",&x,&y,&m,&n,&L)!=EOF){ a = m-n; b = y-x; d = gcd(a,L); if(b % d != 0){ printf("Impossible\n"); }else{ exgcd(a,L,t1,t2); t1 = t1*b/d; //先變換到求at1+Lt2=y-x,再求最小,不能先求最小再變換,不然題意不符 t1 = t1 % L; if(t1<0){ t1 += L; } printf("%lld\n",t1); } } return 0; }