擴展歐幾里得算法1.ax+by=gcd(a,b)ios
擴展歐幾里得算法來解決這樣一個問題:給定兩個非零整數a和b,求一組整數解(x,y),使得ax+by=gcd(a,b)成立,其中gcd(a,b)表示a和b的最大公約數。c++
gcd(a,b)-->gcd(b,a%b)-->.....--->gcd(a,0)最終返回a的值,因此就有一組解成立,a*1+b*0=gcd。此時有x=1,y=0算法
利用上面的式子反推最原始的x和y。spa
當計算gcd(a,b),有ax1+by1=gcd成立;下一步計算gcd(b,a%b)時,有bx2+(a%b)y2=gcd成立。code
所以ax1+by1=bx2+(a%b)y2成立,化簡後blog
x1=y2遞歸
y1=x2-(a/b)y2ci
所以就有啦以下代碼:it
#include<iostream> #include<cstdio> #include<cmath> using namespace std; int exgcd(int a,int b,int &x,int &y)//擴展歐幾里得算法 { if(b==0) { x=1;y=0; return a; //到達遞歸邊界開始向上一層返回 } int r=exgcd(b,a%b,x,y); int temp=x; x=y; //把x y變成上一層的 y=temp-(a/b)*y; return r; //獲得a b的最大公因數 }
獲得最原始的解後,就能夠求所有解io
x'=x+(b/gcd)*k
y'=y+(a/gcd)*k
最小非負整數解:(x%(b/gcd)+b/gcd)%(b/gcd)
例題:https://www.acwing.com/problem/content/879/
#include<iostream> #include<cstdio> #include<cmath> using namespace std; int exgcd(int a,int b,int &x,int &y)//擴展歐幾里得算法 { if(b==0) { x=1;y=0; return a; //到達遞歸邊界開始向上一層返回 } int r=exgcd(b,a%b,x,y); int temp=x; x=y; //把x y變成上一層的 y=temp-(a/b)*y; return r; //獲得a b的最大公因數 } int main() { int a,b,x,y,r,t; cin>>t; while(t--) { cin>>a>>b; r=exgcd(a,b,x,y); cout<<x<<" "<<y<<endl; } return 0; }
若是ax+by=1時,x的最小非負整數就是:(x%b+b)%b
擴展歐幾里得算法2.ax+by=c求解
已知ax+by=gcd的解,那麼兩邊同時乘上c/gcd,即爲ax+by=c的解
所以(x,y)=(cx0/gcd,cy0/gcd),這樣作的充分條件是c%(gcd(a,b))==0
通解:
cx0/gcd+b/gcd*k
cy0/gcd-a/gcd*k
那麼他的最小整數解爲:
x=((cx/gcd)%(b/gcd)+b/gcd)%(b/gcd)
#include<iostream> #include<cstdio> #include<cmath> using namespace std; int gcd(int a,int b) { if(b==0) return a; return gcd(b,a%b); } int exgcd(int a,int b,int &x,int &y)//擴展歐幾里得算法 { if(b==0) { x=1;y=0; return a; //到達遞歸邊界開始向上一層返回 } int r=exgcd(b,a%b,x,y); int temp=x; x=y; //把x y變成上一層的 y=temp-(a/b)*y; return r; //獲得a b的最大公因數 } int main() { int c,r,a,b,x,y; cin>>a>>b>>c; r=exgcd(a,b,x,y); cout<<(c*x/r%(b/r)+b/r)%(b/r)<<endl; return 0; }
擴展歐幾里得算法3.同餘式:ax=c(mod m)的求解
ax=c(mod m)= ( ax-c )%m==0
ax-c=my,令Y=-Y,得
ax+my=c,當c%gcd(a,m)==0時成立,纔有解。
解的形式爲(x,y)=(cx0/gcd(a,m),cy0/gcd(a,m))
通解爲:
x'=x+m/gcd(a,m)*k
y'=y-a/gcd(a,m)*k
最小正整數解爲:x=((cx0/gcd(a,m)%(m/gcd(a,m))+m/gcd)%m/gcd;
例題:線性同餘方程https://www.acwing.com/problem/content/880/
#include<iostream> #include<cstdio> #include<cmath> using namespace std; long long gcd(long long a,long long b) { if(b==0) return a; return gcd(b,a%b); } 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 r=exgcd(b,a%b,x,y); long long temp=x; x=y; //把x y變成上一層的 y=temp-(a/b)*y; return r; //獲得a b的最大公因數 } int main() { long long a,b,x,y,r,m,k,t; cin>>t; while(t--) { cin>>a>>b>>m; if(b%gcd(a,m)==0) { r=exgcd(a,m,x,y); cout<<(((b*x)/r)%(m/r)+m/r)%(m/r)<<endl; } else cout<<"impossible"<<endl; } return 0; }
擴展歐幾里得算法4.逆元的求解以及(b/a)%m的計算
當計算(b/a)%m的值時,咱們須要先算出a模m的逆元x,
就有(b/a)%m=(b*x)%m成立
a模m的逆元就是同餘式:ax=1(mod m)
若是gcd(a,m)=1,就有解,求出x後,就能夠用(x%m+m)%m獲得所須要的逆元。
//在a和m互不是質數的狀況下:(b/a)%m=(b%(am))/a /*ab=1(mod m); 計算(b/a)%m=(b*x)%m其中x爲a模m的逆元 接下來求解ax=1(mod m) 是否有解取決於 1%gcd(a,m)==0 也就是 gcd(a,m)==1則有解。 求出x,在利用(x%m+m)%m)求出最小解 */ #include<iostream> #include<cstdio> #include<cmath> using namespace std; int exgcd(int a,int b,int &x,int &y)//擴展歐幾里得算法 { if(b==0) { x=1;y=0; return a; //到達遞歸邊界開始向上一層返回 } int r=exgcd(b,a%b,x,y); int temp=x; x=y; //把x y變成上一層的 y=temp-(a/b)*y; return r; //獲得a b的最大公因數 } int main() { int a,b,x,y,m,r; cin>>a>>b>>m; r=exgcd(a,m,x,y); x=(x%m+m)%m; cout<<(b*x)%m<<endl; return 0; }
固然也能夠使用費馬小定理:am-2%m 就是a模m的逆元,再用快速冪便可求解
例題:快速冪求逆元https://www.acwing.com/problem/content/878/
#include<bits/stdc++.h> using namespace std; typedef long long ll; ll t,a,p; ll gcd(ll a,ll b) { if(b==0) return a; return gcd(b,a%b); } ll qpow(ll n,ll x) { ll ans=1; while(x) { if(x&1) ans=ans*n%p; n=n*n%p; x>>=1; } return ans; } int main() { ll i,j; cin>>t; while(t--) { cin>>a>>p; if(gcd(a,p)==1) cout<<qpow(a,p-2)%p<<endl; else cout<<"impossible"<<endl; } return 0; }
若是a和m互不質,能夠用公式(b/a)%m=(b%(a*m))/a
證實:存在整數k,使得b/a=km+x,兩邊同時乘以a,得b=kam+ax,因而b%(am)=ax,兩邊同時除以a得,(b%(a*m))/a=x,因而等式成立
注意:a和m的乘積可能太大而溢出。
2019.7.27 擴展歐幾里得算法的理解