[待更新]歐幾里得演算法(輾轉相除法)與擴充歐幾里得演算法

HarlemBlog發表於2024-12-08
更新日誌 2024/12/08:開工。

歐幾里得演算法

用途與原理

歐幾里得演算法,用於求兩個數的最大公約數。

其核心原理為:\(\gcd(a,b)=\gcd(b,a\bmod b)\)

證明

首先,我們證明 \(\gcd(a,b)=\gcd(b,a\bmod b)\)

為了方便證明,這裡我們令 \(a>b\)

\[\because \gcd(a,b)\mid a \text,\gcd(a,b)\mid b\\ \therefore \gcd(a,b)\mid a-\lfloor \frac{a}{b}\rfloor\cdot b\Leftrightarrow \gcd(a,b)\mid a\bmod b\\ \because \gcd(a,b)\mid b,\gcd(a,b)\mid a\bmod b\\ \therefore \gcd(a,b)\mid \gcd(b,a\bmod b) \]

\[\because \gcd(b,a\bmod b)\mid b,\gcd(b,a\bmod b)\mid a\bmod b\\ \therefore\gcd(b,a\bmod b)\mid a\bmod b+\lfloor \frac{a}{b}\rfloor\cdot b\Leftrightarrow \gcd(b,a\bmod b)\mid a\\ \because \gcd(b,a\bmod b)\mid b,\gcd(b,a\bmod b)\mid a\\ \therefore \gcd(b,a\bmod b)\mid \gcd(a,b) \]

\[\because \gcd(a,b)\mid \gcd(b,a\bmod b),\gcd(b,a\bmod b)\mid\gcd(a,b)\\ \therefore \gcd(a,b)=\gcd(b,a\bmod b) \]

證畢。

\(b\mid a\)時,顯然此時 \(\gcd(a,b)=b\)

證畢。

實現細節

  1. 實際實現中,我們無需判斷 \(a>b\),類似於if(a<b)swap(a,b)的操作是不必要的。
    假如 \(a<b\),那麼下一步遞迴時代入的 \(\gcd(b,a\bmod b)=\gcd(b,a)\),可見沒有影響,只多了 \(1\) 的常數。
  2. 更常見的寫法是if(!b)return a,當然if(a%b==0)return b也沒有問題,不過你也可以看出前者更簡潔(實際沒有多少),只是會多出 \(1\) 的常數,但通常情況下%都是很慢的,所以前者其實並不比後者劣。

模板

int gcd(int a,int b){return (b?gcd(b,a%b):a);}

如果你不喜歡三目運算子或者壓行:

int gcd(int a,int b){
    if(!b)return a;
    return gcd(b,a%b);
}

擴充歐幾里得演算法

用途

擴充歐幾里得演算法用來求解形似 \(ax+by=c\) 的線性不定方程。

拓歐常見的利用是求乘法逆元。

原理與證明

首先,我們需要確定上述方程是否有解,根據裴蜀定理,上述方程有解當且僅當 \(\gcd(a,b)\mid c\)

我們考慮如何藉助歐幾里得演算法求解這個方程。事實上,擴充歐幾里得演算法是用來求解 \(ax+by=\gcd(a,b)\) 的,但因為 \(\gcd(a,b)\mid c\),所以我們可以以此為跳板求出原方程的解。

思考 \(ax+by=\gcd(a,b)\)\(x,y\)\(bx'+(a\bmod b)y'=\gcd(b,a\bmod b)\)\(x',y'\) 的關係:

\[\because \gcd(a,b)=\gcd(b,a\bmod b)\\ \therefore ax+by=bx'+(a\bmod b)y'\\ \Leftrightarrow ax+by=bx'+(a-\lfloor\frac{a}{b}\rfloor\cdot b)y'\\ \Leftrightarrow ax+by=ay'+b(x'-\lfloor\frac{a}{b}\rfloor\cdot y')\\ \Rightarrow x=y',y=x'-\lfloor\frac{a}{b}\rfloor\cdot y' \]

這樣,我們在求 \(\gcd(a,b)\) 的同時,即可求出 \(x,y\)

考慮邊界情況,當 \(b\mid a\) 時,方程式形如:

\[ax+by=b \]

顯然此時一組可行解為 \(x=0,y=1\),然後回溯即可。

但並沒有結束,我們得到的是 \(ax+by=\gcd(a,b)\) 的解,令之為 \(x',y'\),不難發現原方程的解為:

\[a\frac{c}{\gcd(a,b)}x'+b\frac{c}{\gcd(a,b)}y'=c\\ \therefore x=\frac{c}{\gcd(a,b)}x',y=\frac{c}{\gcd(a,b)}y' \]

實現細節

  1. 考慮到 \(\gcd\) 我們會在 \(b=0\) 時退出(如果不是這種寫法請忽略),此時應使 \(x=1,y=0\),這樣回溯一步就可以使 \(b|a\)\(ax+by=b\) 的解為 \(x=0,y=1\)
  2. 上述透過 \(x',y'\)\(x,y\) 賦值的操作有更簡潔的寫法,可以直接向下傳參的時候 \(x'\gets y,y'\gets x\) 且使用引用傳參,返回之後 \(y\gets y-\lfloor\frac{a}{b}\rfloor\cdot x\) 即可。

模板

int exgcd(int a,int b,int &x,int &y){
    if(!b){
        x=1,y=0;
        return a;
    }
    int d=exgcd(b,a%b,y,x);
    y-=(a/b)*x;
    return d;
}

求乘法逆元

使用 \(\text{exgcd}\) 求乘法逆元的前提是模數與被求的數互質。原因下面會說。

考慮乘法逆元定義:\(ax\equiv 1\pmod p\),其中 \(x\) 即為 \(a\) 逆元。

我們展開原式可得:

\[ax-\lfloor\frac{a}{p}\rfloor p=1\\ \Leftrightarrow ax+py=1 \]

那麼就可以用拓歐求解了。根據裴蜀定理,有解的前提是 \(\gcd(a,p)\mid 1\),等同於 \(a,p\) 互質。

最後的 \(x\) 無需除以 \(\text{gcd}\) 然後再乘 \(1\),因為這兩個都是 \(1\)

使用格式大概如下:

int x,y;
exgcd(a,p,x,y);

然後 \(x\) 就是 \(a\)\(p\) 意義下的逆元了。

相關文章