読者です 読者をやめる 読者になる 読者になる

超わかりやすい拡張ユークリッドの互除法

ユークリッドの互除法を考える。

数a,bが与えられる。
1. 最初はx=a, y=bとする。
2. xをyで割った余りにする。
3. yをxで割った余りにする。
4. 2に戻る。
5. xかyが0になったときに、もう一方が最大公約数である。

ここで、この数x,yを、別の方法で表現することを考える。つまり

x = a * x' + b * x''
y = a * y' + b * y''

まず、最初の初期化では次のようになる。

x = a * 1 + b * 0 = a
y = a * 0 + b * 1 = b

また、この形式を保持しながら、「余り」の計算を行うことが可能である。
xをyで割った余りをmod、割った整数の商をdivとすると、

mod = x - y * div

が成立することを利用する。

         y = a *    y'    + b *    y''
=> div * y = a * (div*y') + b * (div*y'')

         x = a *    x'    + b *    x''
+) div * y = a * (div*y') + b * (div*y'')
-------------------------------------------------
       mod = a * (x'-div*y') + b * (x''-div*y'')

この計算を、a%bの計算のついでに行うだけで、
最終的には

gcm = a * something + b * something

という形の式が得られる。

というわけで、簡単な拡張ユークリッドのデモを書いた。

#include <cstdio>
#include <utility>
#include <algorithm>

using namespace std;

pair<int, pair<int, int> > ee(int a, int b) {
    pair<int, pair<int, int> > x(a,make_pair(1,0));
    pair<int, pair<int, int> > y(b,make_pair(0,1));
    int div;
    while(true) {
        if(!y.first)return x;
        div = x.first/y.first;
        x.first         = x.first         - div * y.first;
        x.second.first  = x.second.first  - div * y.second.first;
        x.second.second = x.second.second - div * y.second.second;
        if(!x.first)return y;
        div = y.first/x.first;
        y.first         = y.first         - div * x.first;
        y.second.first  = y.second.first  - div * x.second.first;
        y.second.second = y.second.second - div * x.second.second;
    }
}
// mod and num must be co-prime.
int inverse(int mod, int num) {
    pair<int, pair<int, int> > eret = ee(num, mod);
    return ((eret.second.first%mod)+mod)%mod;
}
// mod must be prime number.
int inverse2(int mod, int num) {
    int pw = mod-2;
    int ret = 1;
    for(int i = 30; i >= 0; i--) {
        ret = (ret*ret)%mod;
        if((pw>>i)&1) {
            ret = (ret*num)%mod;
        }
    }
    return ret;
}

int main(int argc, char **argv) {
    pair<int, pair<int, int> > eret = ee(936, 102);
    printf("936 * %d + 102 * %d = %d\n", eret.second.first, eret.second.second, eret.first);
    for(int i = 1; i < 47; i++) {
        int inv = inverse(47, i);
        int inv2 = inverse2(47, i);
        printf("inverse:  %2d * %2d === 1 (mod 47)\n", i, inv);
        printf("inverse2: %2d * %2d === 1 (mod 47)\n", i, inv2);
    }
    return 0;
}

剰余環における逆元を拡張ユークリッドで求めるのだが、同時にフェルマーの小定理とバイナリ法を利用した方法(こちらは素数を法とした場合しか計算できない)も示した。

他には、拡張ユークリッドを使うと、中国人剰余定理に応用できるので、これをさらに百五減算とか多倍長とかに応用できると思う。