分析教材把歐幾里得演算法與快速冪當成 (O(\log n)) 的例子,沒有把它們接成「模意義下的除法」。競賽與密碼學導論裡,這四個是同一條鏈:gcd 判斷互質,擴展版求出係數,係數就是模反元素,快速冪用來算大指數。本篇只做到正確計算與複雜度,不討論如何攻擊實際系統。

相關教材:Chapter2_Algorithm_Analysis_Teaching_Materials.md、Divide_And_Conquer_Teaching_Materials.md。


目錄

  1. 歐幾里得演算法
  2. 擴展歐幾里得
  3. 模反元素
  4. 快速冪
  5. 模運算的幾個恆用規則
  6. 常見陷阱
  7. C++ 實作
  8. 練習題

1. 歐幾里得演算法

(\gcd(a,b)=\gcd(b, a \bmod b)),直到餘數是 0。另一個數就是最大公因數。

例:(\gcd(252, 105))。

a b a mod b
252 105 42
105 42 21
42 21 0

(\gcd=21)。(252/21=12),(105/21=5),確實整除。

除法次數是 (O(\log \min(a,b)))。最慢的是連續費氏數,仍然是對數。這就是分析教材所說的對數型演算法。

負數先取絕對值。(\gcd(0, n)=|n|),(\gcd(0,0)) 沒有有用的定義,呼叫端不要傳。


2. 擴展歐幾里得

除了 (d=\gcd(a,b)),還求整數 (x,y) 使

[ ax + by = d ]

回代:

[ \begin{align} 252 &= 2\cdot 105 + 42 \ 105 &= 2\cdot 42 + 21 \ 42 &= 2\cdot 21 + 0 \end{align} ]

從 21 往回代:

[ 21 = 105 - 2\cdot 42 = 105 - 2\cdot(252 - 2\cdot 105) = 5\cdot 105 + (-2)\cdot 252 ]

所以 (252\cdot(-2) + 105\cdot 5 = 21)。

實作不要先遞迴再手代。讓遞迴在回來的路上直接組係數:(\gcd(b, a\bmod b)) 得到 (bx_1 + (a\bmod b)y_1 = d),而 (a\bmod b = a - \lfloor a/b\rfloor b),整理成 (a) 與 (b) 的係數即可。


3. 模反元素

在模 (m) 下,(a) 的反元素是整數 (a^{-1}) 使 (a\cdot a^{-1} \equiv 1 \pmod m)。它存在若且唯若 (\gcd(a,m)=1)。

那時擴展歐幾里得給出 (ax + my = 1),所以 (ax \equiv 1 \pmod m),(x \bmod m) 就是反元素。

例:(a=3),(m=11)。

[ \begin{align} 11 &= 3\cdot 3 + 2 \ 3 &= 1\cdot 2 + 1 \ 2 &= 2\cdot 1 + 0 \end{align} ]

[ 1 = 3 - 1\cdot 2 = 3 - 1\cdot(11-3\cdot 3) = 4\cdot 3 + (-1)\cdot 11 ]

反元素是 4。檢查:(3\cdot 4=12\equiv 1\pmod{11})。

(\gcd) 不是 1 就沒有反元素。例如 (\gcd(2,4)=2),不要回傳一個「看起來能乘」的數。費馬小定理 (a^{p-2} \equiv a^{-1}\pmod p) 只在 (p) 是質數且 (p\nmid a) 時可用,那是快速冪的另一條路,模是合數時會悄悄給錯。


4. 快速冪

計算 (a^e \bmod m)。不要先算出巨大的 (a^e) 再取模。

把指數寫成二進位。(13 = 1101_2 = 8+4+1),所以 (a^{13}=a^8\cdot a^4\cdot a^1)。從 (a) 開始反覆平方,指數的該位元是 1 就把目前的平方乘進答案。每一步都取模。

(3^{13} \bmod 10):

平方 值 mod 10 指數位元(從低位) 答案
(3^1) 3 1 3
(3^2) 9 0 3
(3^4) 1 1 3
(3^8) 1 1 3

(3^{13}=1594323),個位數確實是 3。時間 (O(\log e)) 次乘法。底數是矩陣時,同一個演算法就是矩陣快速冪,用來加速線性遞迴,乘法的常數換成矩陣大小。


5. 模運算的幾個恆用規則

設 (m>0)。

[ (x+y)\bmod m = (x\bmod m + y\bmod m)\bmod m ]

[ (x\cdot y)\bmod m = ((x\bmod m)\cdot(y\bmod m))\bmod m ]

減法可能得到負數。程式語言的 % 對負數的符號並不一致。需要非負代表時,寫成 (((x \bmod m)+m)\bmod m)。

除法不是乘上「小數」。模 (m) 下的除以 (a),是乘上 (a^{-1}),而且只有反元素存在時才合法。

兩個 64 位元整數相乘再取模,若先相乘會溢位。需要「乘法取模」時用更大的寬度,或用專門的無溢位寫法。本篇的範例數字小,不依賴那個技巧。


6. 常見陷阱

  • gcd(0,0)。 迴圈條件要先定義。
  • 反元素不存在仍回傳係數。 先看 (\gcd) 是不是 1。
  • 快速冪用 int 乘 int。 即使最後要取模,中間乘積也會溢位。乘法前轉成 64 位元,並在模數大於 (2^{32}) 時改用更寬的乘法。
  • 負的指數。 那是反元素加上正指數,不是把迴圈跑負次數。
  • 費馬小定理用在合數模。 反元素改走擴展歐幾里得。
  • 把本篇算法拿去猜密碼或分解實際金鑰。 正確實作這些函數,和攻擊一個系統,是兩件事。本教材只負責前者。

7. C++ 實作

#include <cstdint>
#include <utility>

long long egcd(long long a, long long b, long long& x, long long& y) {
    if (b == 0) { x = 1; y = 0; return a; }
    long long x1, y1;
    long long d = egcd(b, a % b, x1, y1);
    x = y1;
    y = x1 - (a / b) * y1;
    return d;
}

long long mod_inverse(long long a, long long m) {
    long long x, y;
    long long d = egcd(a, m, x, y);
    if (d != 1 && d != -1) return -1;  // 不存在
    return (x % m + m) % m;
}

long long mod_pow(long long a, long long e, long long m) {
    long long r = 1 % m;
    a %= m;
    if (a < 0) a += m;
    while (e > 0) {
        if (e & 1) r = r * a % m;
        a = a * a % m;
        e >>= 1;
    }
    return r;
}

egcd 在 a、b 為負時,餘數的符號依 C++ 實作走向零的除法,係數仍滿足等式,但模反元素的入口請先把 a 化成 ([0, m))。


8. 練習題

  1. 計算 (\gcd(48, 18)),並寫出一組 (48x+18y=\gcd)。
  2. 11 模 26 的反元素是多少?用乘法檢查。
  3. (2^{10} \bmod 1000) 用快速冪,平方序列是什麼?
  4. 為什麼 (\gcd(15, 25)) 算得出係數,卻沒有模 25 的反元素?
  5. 分析教材說快速冪是 (O(\log N))。主定理的 (a)、(b)、(f(n)) 各是什麼?

參考答案

  1. (48=2\cdot 18+12),(18=1\cdot 12+6),(12=2\cdot 6+0),所以 (\gcd=6)。回代:(6=18-1\cdot 12=18-1\cdot(48-2\cdot 18)=3\cdot 18 + (-1)\cdot 48)。一組解是 (x=-1, y=3)。
  2. (\gcd(11,26)=1)。回代得到 (11\cdot(-7)+26\cdot 3=1)。(-7 \bmod 26 = 19)。檢查:(11\cdot 19=209=8\cdot 26+1)。
  3. 平方序列取模 1000:(2, 4, 16, 256)。(10=1010_2),所以答案是 (2^8\cdot 2^2 = 256\cdot 4 = 1024 \equiv 24 \pmod{1000})。(2^{16}) 不用算。
  4. 擴展演算法給的是 (15x+25y=5),不是 1。模 25 下 15 與 25 不互質,沒有整數乘上 15 會得到 1。
  5. 每次指數減半、只做一個子問題、再加上常數次乘法:(a=1),(b=2),(f(n)=\Theta(1))。這是主定理情況 2,(T(n)=\Theta(\log n))。

小結

歐幾里得演算法用餘數把最大公因數壓到對數時間。擴展版多回傳一組係數。係數在互質時就是模反元素。快速冪用平方把指數的位元變成對數次乘法,每一步取模。四個合在一起,才是模意義下的加減乘除。反元素不存在時要明白回傳失敗,不要用費馬小定理去算合數模。