[牛客網]A Number Theoretical Problem
題目鏈接:https://ac.nowcoder.com/acm/problem/207599
這貌似是一道求逆元的模板題,但是。。。
逆元是什么啊!!!擴展歐幾里得是什么啊!!!
於是我今天花了一下去看別人的博客,一臉懵逼的進去,一臉懵逼的出來。
其實有很多地方我都搞不明白,但也管不了那么多啦,把我搞明白的講出來就可以了
首先我們都知道歐幾里得算法(也叫輾轉相除法)能求出兩個數a,b的最大公約數,即gcd(a,b)=gcd(b,a%b)。
復雜度是o(ln n)
代碼實現如下:
#include <iostream> using namespace std; int gcd(int a,int b); int main(){ int a,b; scanf("%d %d",&a,&b); printf("%d %d\n",a,b); printf("gcd=%d\n",gcd(a,b)); } int gcd(int a,int b){ if(b==0) return a; int ans=gcd(b,a%b); return ans; }
再次基礎上的擴展歐幾里能快速求ax+by=gcd(a,b)一個二元一次方程的一個特解:
當歐幾里得算法遞歸到最后一層,即b==0時,那么gcd=a,這就很容易得到一個特解 x=1,y=0,即a*x+b*y=a*1+b*0=a=gcd
得到特解x=1,y=0,在通過遞歸的返回,再來一層一層推出原本 ax+by=gck,的一組特解
關於如何推出上一層的特解在這里我們把上一層的解x,y。當前層已知的解為x1,y1
在這里的一個結論是 a%b=a-(a/b)*b (這里的 “/” 指的是整除,例如 5/2=2 , 1/3=0)
由於上一層的gcd(a,b)等於當前層的gcd(b,a%b)
即gcd(b,a%b)=b*x1+(a%b)*y1
=b*x1+(a-(a/b)*b)*y1
=b*x1+ a*y1-(a/b)*b*y1
=a*(y1)+b*(x1-(a/b)*y1)
又因為gcd(a,b)=a*x+b*y,即這兩式子相等,通過比較得 x=y1,y=x1-(a/b)*y1。
int gcd(int a,int b,int &x,int &y){ if(b==0){ x=1,y=0; \\ 一組特解 return a; } int ans=gcd(b,a%b,x,y); int t=x; \\由最后一層的一組特解向上推 x=y; y=t-(a/b)*y; return ans; }
如果只能擴展歐幾里快速求ax+by=gcd(a,b)一個二元一次方程的一個特解,可能會說:“ 就這???”
這里我們就要知道擴展歐幾里快速求乘法逆元
然后就是逆元的定義
什么叫乘法逆元?
a*x≡1(mod n) (≡為同余符號,即(a*x)mod n==1 mod n)
這里,我們稱 x 是 a 關於 n 的乘法逆元
判斷逆元是否存在為gcd(a,n)是否等於1,等於即存在,否則不存在。
我們可以把他寫成一個表達式為:a*x+b*y=1(這里設b=n)
於是求逆元就要求這個二元一次方程組,則通過上面所說的擴展歐幾里得
但是有無數解,但一般會讓你求最小解x0,其實x0mod n就是最小解(這我真搞不明白)
考慮到x0可能是為負數,那我們先對其取模,x0mod n ,但x0還是負數,我們再加上其n,最后再取模。即 x0=(x0%n+n)%n
於是對於牛客上那道求逆元的模板題我們就迎刃而解了
代碼如下:
#include <iostream> typedef long long LL; using namespace std; LL exgcd(LL a,LL b,LL &x,LL &y); int t; LL y,p; int main(){ scanf("%d",&t); while(t--){ scanf("%lld %lld",&y,&p); LL x=0,k=0; LL ok=exgcd(y,p,x,k); if(ok==1) printf("%lld\n",(x%p+p)%p); else printf("-1\n"); } } LL exgcd(LL a,LL b,LL &x,LL &y){ if(b==0){ x=1,y=0; return a; } int ans=exgcd(b,a%b,x,y); LL t=x; x=y; y=t-(a/b)*y; return ans; }