LoginSignup
0
0

More than 3 years have passed since last update.

拡張ユークリッドの互除法を理解する

Last updated at Posted at 2021-06-03

拡張ユークリッドの互除法とは?

${\rm gcd}(a,b)$ を求めるついでに $xa + yb = {\rm gcd}(a,b)$ の解 $(x, y)$ もついでに求めるアルゴリズムです

ユークリッドの互除法復習

とりあえず147と60のgcdを求めてみましょう

\begin{alignat}{2}
& & & & 147 \\
& & & & 60 \\
147 & \quad \% \quad & 60 \quad = \quad & & 27 \\
60 & \quad \% \quad & 27 \quad = \quad & & 6 \\
27 & \quad \% \quad & 6 \quad = \quad & & 3 \\
6 & \quad \% \quad & 3 \quad = \quad & & 0 \\
\end{alignat}

よって ${\rm gcd}(147,60) = 3$ です
見ての通り $z_0 = 147, z_1 = 60, z_i = z_{i-2} \% z_{i-1}$ という隣接2項間漸化式ですね
フィボナッチ数列みたいな感じです

これを素直に実装するとすれば以下のような感じでしょうか

int gcd(int a, int b){
    int z[1000] = { a, b };
    for(int i = 2; ; ++i){
        int q = z[i-2] / z[i-1];
        z[i] = z[i-2] - z[i-1] * q;
        if(z[i] == 0) return z[i-1];
    }
}

後の説明が楽になるようにmod演算ではなく除算と引き算を使って実装しました

拡張ユークリッドの互除法

ここで $z_i = z_{i-2} - z_{i-1} * q_i, \quad q_i = {\rm floor}(z_{i-2} / z_{i-1})$ という処理を
$xa + yb = z$ という形の数式に対して実行してみましょう

\begin{alignat}{5}
1 & \quad \times \quad & 147 & \quad + \quad & 0 & \quad \times \quad & 60 & \quad = \quad & 147 & \qquad (1) \\
0 & \quad \times \quad & 147 & \quad + \quad & 1 & \quad \times \quad & 60 & \quad = \quad & 60 & \qquad (2) \\
1 & \quad \times \quad & 147 & \quad + \quad & (-2) & \quad \times \quad & 60 & \quad = \quad & 27 & \qquad (3) = (1) - 2 \times (2) \\
(-2) & \quad \times \quad & 147 & \quad + \quad & 5 & \quad \times \quad & 60 & \quad = \quad & 6 & \qquad (4) = (2) - 2 \times (3) \\
9 & \quad \times \quad & 147 & \quad + \quad & (-22) & \quad \times \quad & 60 & \quad = \quad & 3 & \qquad (5) = (3) - 4 \times (4) \\
& & & \quad \text{(略)} &  &  &  & \quad = \quad & 0 & \qquad (6) = (7) - 2 \times (8) \\
\end{alignat}

ということで
${\rm gcd}(147,60) = 3$ を求めるついでに $xa + yb = {\rm gcd}(147,60) = 3$ の解 $(x, y) = (9, -22)$ が求まりました。

先ほどと同じように実装するとこんな感じでしょうか?

struct eq {
    int x,y,z;
    eq operator*(int q)const{ return { x*q, y*q, z*q }; }
    eq operator-(const eq &b)const{ return { x-b.x, y-b.y, z-b.z }; }
};
eq extgcd(int a, int b){
    eq e[1000] = { {1,0,a}, {0,1,b} };
    for(int i = 2; ; ++i){
        int q = e[i-2].z / e[i-1].z;
        e[i] = e[i-2] - e[i-1] * q;
        if(e[i].z == 0) return e[i-1];
    }
}

フィボナッチ数列を求める時と同じように過去2回分まで結果を持っておけば十分なので整理すると

std::tuple<int,int,int> extgcd(int a, int b){
    int x=1,y=0;
    int u=0,v=1;
    while(b){
        int q = a / b;
        x -= u * q;
        y -= v * q;
        a %= b;
        std::swap(x, u);
        std::swap(y, v);
        std::swap(a, b);
    }
    return {x,y,a};
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0