モジュロ演算(剰余環)の復習

モジュロ演算のアルゴリズムについて勉強をした。逆元や二項係数の効率的な計算などはスニペットにした。 ここには勉強したことのメモを残す。二項係数は書く時間なかった。

読んだのは下あたり。

関係するけど下は通読できてない。

関連する知識

Fermatの小定理

素数のとき。

\[ a^{p-1} \equiv 1 \mod p \]

Eularのtotient関数

\( \varphi(n)\) をEularのtotuient関数として以下が成り立つ。

\[ \begin{aligned} a^{\varphi(n)-1} & \equiv 1 \mod n \\ \varphi(n) &= \vert \{m | 1 \le m \le n \wedge \gcd(m, n) = 1, \}| \end{aligned} \]

ユークリッド互除法(Euclidean Algorithm)

自然数の最大公約数を求める時に使う。 \( a, b\) の公約数は \( b = qa + r\) とした際の \( b, r\) の最大公約数と等しい。 また \( \max(a, b)\) は単調減少する。 そのため剰余はいずれ \( 0\) となる。 その時の除数が最大公約数となる。

拡張ユークリッド互除法は、以下の方程式で \( a, b\) から \( x, y, d\) を求めことに使える。 ここで \( d\) は最大公約数(\( gcd(a, b), a \le b\))。

\[ ax + by = d \]

\( b = qa + r\) と置き換えて \( rx' + ay' = d\) とすると下が得られる。

\[ \begin{aligned} y &= x' \\ x &= y' - b/a \end{aligned} \]

また最大公約数が求まる時の式は以下になる

\[ ax + qay = a \]

これは \( x = 1, y = 0\) とすると成立する。 これをGoでコードにすると下になる。(ループで書いてるのでわかりにくい)

1
2
3
4
5
6
7
8
9
func expGcd(a, b int) (x, y, d int) {
        u, v := 1, 0
        for b > 0 {
                t := a / b
                a, b = b, a-t*b
                u, v = v, u-t*v
        }
        return u, v, a
}

素数pを法とする剰余群の逆元

フェルマーの小定理から \( a^{-1} \equiv a^{p-2}\) となる。 拡張ユークリッド互除法でも \( ax-np=1\) を解くことで得られる。 expGcd() とほとんど変わらないが剰余を取っている。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// moduloInv はmoduloを法とする剰余環でのaの逆元を求める
func moduloInv(a, modulo int) int {
        b := modulo
        u, v := 1, 0
        for b > 0 {
                t := a / b
                a, b = b, a-t*b
                u, v = v, u-t*v
        }
        u %= modulo
        if u < 0 {
                u += modulo
        }
        return u
}

離散対数を求めたい

効率的なアルゴリズムは見つかっていないが擬多項式時間のアルゴリズムは存在する。 Baby-step giant-step法で \( O(\sqrt n)\) で求められる。平方分割して探索するものをBaby-step giant-step法と呼ぶらしい。

Goでの具体例は下の通り。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
func moduloLog(a, b, modulo int) int {
        // log_a(b)
        a %= modulo
        b %= modulo
        m := int(math.Sqrt(float64(modulo)))

        // basy step
        values := map[int]int{}
        val := 1
        for i := 0; i <= m+1; i++ {
                if _, ok := values[val]; !ok {
                        values[val] = i
                }
                val = moduloMul(val, a, modulo)
        }

        // giant step
        compound := moduloInv(moduloPow(a, m, modulo), modulo)
        val = b
        for i := 0; i <= m+1; i++ {
                if l, ok := values[val]; ok {
                        return (i*m%modulo + l) % modulo
                }
                val = moduloMul(val, compound, modulo)
        }
        return -1
}

func moduloMul(a, b, modulo int) int {
        return a % modulo * b % modulo
}

// moduloInv はmoduloを法とする剰余環でのaの逆元を求める
func moduloInv(a, modulo int) int {
        b := modulo
        u, v := 1, 0
        for b > 0 {
                t := a / b
                a, b = b, a-t*b
                u, v = v, u-t*v
        }
        u %= modulo
        if u < 0 {
                u += modulo
        }
        return u
}

func moduloPow(a, b, modulo int) int {
        res := 1
        for b > 0 {
                if b&1 == 1 {
                        res = res * a % modulo
                }
                a = a * a % modulo
                b >>= 1
        }
        return res
}

オイラー関数とか剰余環とか調べていたら、数学を復習したくなった。

comments powered by Disqus