1. 程式人生 > >擴充套件歐幾里得(exgcd)與同餘詳解

擴充套件歐幾里得(exgcd)與同餘詳解

exgcd入門以及同餘基礎

gcd,歐幾里得的智慧結晶,資訊競賽的重要演算法,數論的...(編不下去了

講exgcd之前,我們先普及一下同餘的性質:

  1. \left ( a+b \right )\mod p=\left (a\mod p+ b\mod p\right )\mod p
  2. \left ( a-b \right )\mod p=\left (a\mod p- b\mod p\right )\mod p
  3. \left ( a\times b \right )\mod p=\left (a\mod p)\times (b\mod p\right )\mod p
  4. a\equiv b(\mod p),那麼a^{n}\equiv b^{n}(\mod p)
  5. a\mod p1=xa\mod p2=x,且p1,p2互質,a\mod (p\times q)=x
  6. a\equiv b(\mod p),k和c為整數,而且k>0,那麼a^{k}c\equiv b^{k}c(\mod p)
  7. ac\equiv bc(\mod p),那麼a\equiv b(\mod \frac{p}{gcd(p,c)})就可以推出gcd(p,c)=1,則有a\equiv b(\mod p)

 有了這三個式子,就不用怕在計算時溢位了。

下面我會用gcd\left ( a,b \right )lcm\left ( a,b \right )分別表示a與b的最大公約數與最小公倍數。

首先會來學擴歐的同學肯定都會歐幾里得演算法(即輾轉相除法)了吧

而通過觀察發現:lcm\left ( a,b \right )=\frac{a\times b}{gcd\left ( a,b \right )}=\frac{a}{gcd\left ( a,b \right )}\times b,先除後乘防溢位。

所以gcd\left ( a,b \right )lcm\left ( a,b \right )的程式碼如下:

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之前先引入一種方程——不定方程

所謂不定方程,是指未知數的個數多於方程個數,且未知數受到某些限制(如要求是有理數、整數或正整數等等)的方程或方程組。
                                                                                          ————百度百科

就是形如ax+by=c的方程,其中a,b,c已知。

1.判斷是否有解

如果c\mod gcd\left ( a,b \right )\neq 0,那麼方程無解。

2.轉化

方程可轉化為{a}'x+{b}'y={c}'

其中{a}'=\frac{a}{gcd\left (a,b \right )}{b}'=\frac{b}{gcd\left (a,b \right )}{c}'=\frac{c}{gcd\left (a,b \right )}

3.求一組特解

接著就用到了exgcd。

我們知道gcd有一個性質gcd(a,b) = gcd(b, a\mod b)

如果,一直迴圈下去,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;
}

這是求ax+by \right =gcd\left ( a,b \right )時用的,後面講同餘方程會講。

4.構造通解

我們假設x1,y1是我們求出的一組特解,那麼

\left\{\begin{matrix} & &x=c\cdot x1+c\cdot b\cdot k \\ & & y=c\cdot y1-c\cdot a\cdot k \end{matrix}\right.     \left ( k\epsilon z \right )

同餘類問題

1.單個同餘方程

求的是ax\equiv b( \mod p)關於x的解

轉化一下,就成了ax+py=b,就可以直接套exgcd模板。

2.同餘方程組

\left\{\begin{matrix} & &x\equiv b1\left (\mod p1 \right )\\ & & x\equiv b2\left (\mod p2 \right ) \end{matrix}\right.

1.有解的充要條件\left ( b1-b2 \right )\mod gcd(p1,p2)=0

2.\left\{\begin{matrix} & &x-p1\times y1=b1\\ & & x-p2\times y2=b2\end{matrix}\right.

下式減上式得p1\times y1-p2\times y2=b2-b1

再用exgcd求出y1和y2,{x}'=b1+p1\times y1=b2+p2\times y2

3.關於通解

所有的x mod lcm(p1,p2)有唯一解,這樣就可以通過特解,求通解了。

4.至於式子更多的同餘方程組,就先聯立兩個,就可以得出新的方程x\equiv {x}'\left ( \mod lcm(p1,p2) \right )

再聯立下一個。

exgcd用處及題目講解

1.求同餘方程的解

例如這道題P1082

這是一道裸的擴歐模板題,變形之後就是求ax+by=1

套模板即可。

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+k\times m\equiv y+k\times n(\mod l)的解。

進而可以推出x+k\times m-y-k\times n=l\times p

合併同類項後(x-y)+k\times(m-n)-l\times p=0

把一些東西移到左邊來後(x-y)=k\times(n-m)+l\times p

把(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,謝謝