擴充套件歐幾里得(exgcd)與同餘詳解
exgcd入門以及同餘基礎
gcd,歐幾里得的智慧結晶,資訊競賽的重要演算法,數論的...(編不下去了
講exgcd之前,我們先普及一下同餘的性質:
- 若,那麼
- 若,,且p1,p2互質,
- 若,k和c為整數,而且k>0,那麼
- 若,那麼就可以推出,則有
有了這三個式子,就不用怕在計算時溢位了。
下面我會用與分別表示a與b的最大公約數與最小公倍數。
首先會來學擴歐的同學肯定都會歐幾里得演算法(即輾轉相除法)了吧
而通過觀察發現:,先除後乘防溢位。
所以與的程式碼如下:
inline int gcd(int a,int b) {return (b==0)?a:gcd(b,a%b);} inline int lcm(int a,int b) {return a/gcd(a,b)*b;}
講exgcd之前先引入一種方程——不定方程
所謂不定方程,是指未知數的個數多於方程個數,且未知數受到某些限制(如要求是有理數、整數或正整數等等)的方程或方程組。
————百度百科
就是形如的方程,其中a,b,c已知。
1.判斷是否有解
如果,那麼方程無解。
2.轉化
方程可轉化為,
其中,,
3.求一組特解
接著就用到了exgcd。
我們知道gcd有一個性質
如果,一直迴圈下去,b將等於0,那麼x將等於c/a,y=0。
inline void exgcd(int a,int b,int c)
{
if(!b)
{x=c/a;y=0;return;}
exgcd(b,a%b,c);
x=y;
y=(c-a*x)/b;
return;
}
這就求出了一組特解。
exgcd的模板我也在這擺出來
inline void exgcd(int a,int b)
{
if(!b)
{x=1;y=0;return;}
exgcd(b,a%b);
k=x;x=y;
y=k-a/b*y;
return;
}
這是求時用的,後面講同餘方程會講。
4.構造通解
我們假設x1,y1是我們求出的一組特解,那麼
同餘類問題
1.單個同餘方程
求的是關於x的解
轉化一下,就成了,就可以直接套exgcd模板。
2.同餘方程組
1.有解的充要條件
2.
下式減上式得
再用exgcd求出y1和y2,
3.關於通解
所有的x mod lcm(p1,p2)有唯一解,這樣就可以通過特解,求通解了。
4.至於式子更多的同餘方程組,就先聯立兩個,就可以得出新的方程
再聯立下一個。
exgcd用處及題目講解
1.求同餘方程的解
例如這道題P1082
這是一道裸的擴歐模板題,變形之後就是求。
套模板即可。
inline void exgcd(int a,int b)
{
if(b==0)
{x=1;y=0;return;}
exgcd(b,a%b);
k=x;x=y;
y=k-a/b*y;
return;
}
int main()
{
int n,m;
read(n),read(m);
exgcd(n,m);
printf("%d",(x+m)%m);
}
我校某退役選手暴力80分,%%(你谷資料水
int main()
{
long long int a,b,c,d=0;
int j=0;
io::begin();
io::read(a);io::read(b);
while(d!=1)
{
++j;d=a+d;
while(d>b)d-=b;
}
write(j);
return 0;
}
還有一道模板P1516
仔細觀察,推一下後我們發現,這在就是在求的解。
進而可以推出
合併同類項後
把一些東西移到左邊來後
把(x-y),(n-m)各看成一個整體後,問題就成了解一個不定方程。
inline int exgcd(long long a,long long b)
{
if(b==0)
{x=1;y=0;return a;}
ans=exgcd(b,a%b);
k=x;x=y;
y=k-a/b*y;
return ans;
}
int main()
{
long long x1,y1,m,n,l;
read(x1),read(y1),read(m),read(n),read(l);
if(n-m<0)swap(x1,y1);
exgcd(std::abs(n-m),l);
if((x1-y1)%ans!=0)
printf("Impossible");
else
printf("%lld",((x*((x1-y1)/ans))%(l/ans)+(l/ans))%(l/ans));
}
還有一道也是模板P4777,涉及同餘方程組求解,上面已詳細的講了,近期我也會發一篇中國剩餘定理的部落格
inline long long mul(long long a,long long b,long long mod)
{
long long res=0;
while(b>0)
{
if(b&1) res=(res+a)%mod;
a=(a+a)%mod;
b>>=1;
}
return res;
}
long long exgcd(long long a,long long b,long long &x,long long &y)
{
if(!b)
{x=1;y=0;return a;}
long long gcd=exgcd(b,a%b,x,y);
k1=x;x=y;
y=k1-a/b*y;
return gcd;
}
int main()
{
io::begin();
io::read(n);
for(register int i=1;i<=n;i++)
io::read(b1[i]),io::read(a1[i]);
long long x,y,k;
long long m=b1[1],ans=a1[1];
for(int i=2;i<=n;i++)
{
long long a=m,b=b1[i],c=(a1[i]-ans%b+b)%b;
long long gcd=exgcd(a,b,x,y);
long long p=b/gcd;
x=mul(x,c/gcd,p);
ans+=x*m;
m*=p;
ans=(ans%m+m)%m;
}
printf("%lld",(ans%m+m)%m);
}
2.擴歐求逆元
這是一種很重要的演算法,至於逆元怎麼跟擴歐扯上關係,大家可以點這裡乘法逆元及兩道模板題詳解
這裡就不多贅述了,大家可以用擴歐a一下P3811,P2613。
我要講的講完了,如果覺得講的還好,請關注我的blog,謝謝