Linear Congruence & Chinese Remainder Theorem

fn gcd(a: i64, b: i64) -> i64 {
    if b == 0 {
        a
    } else {
        gcd(b, a.rem_euclid(b))
    }
}

fn extgcd(a: i64, b: i64) -> (i64, i64, i64) {
    if b == 0 {
        (1, 0, a) // (x0, y0, g)
    } else {
        let (x1, y1, g) = extgcd(b, a.rem_euclid(b));
        (y1, x1 - y1 * (a / b), g) // (x0, y0, g)
    }
}

fn minv(a: i64, m: i64) -> i64 {
    let (x0, _, _) = extgcd(a, m);
    x0.rem_euclid(m)
}

// ax = b (mod m) has solution
//  x = (a/g)^(-1) * (b/g) (mod m/g)
fn linear_congruence(a: i64, b: i64, m: i64) -> Option<i64> {
    let (inv, _, g) = extgcd(a, m);
    if b % g != 0 {
        None
    } else {
        Some((inv * (b / g)).rem_euclid(m / g))
    }
}

// x = r1 (mod m1)
// x = r2 (mod m2) has solution
// x = m1 q1 + r1 (mod lcm(m1, m2)) where
// q1 = linear_congruence(m1, r2 - r1, m2)
fn crt(coef: &Vec<(i64, i64)>) -> Option<(i64, i64)> {
    let mut m1 = coef[0].0;
    let mut r1 = coef[0].1;
    for i in 1..coef.len() {
        let (m2, r2) = coef[i];
        if let Some((q1, g)) = linear_congruence(m1, r2 - r1, m2) {
            let lcm = m1 / g * m2;
            r1 = (m1 * q1 + r1).rem_euclid(lcm);
            m1 = lcm;
        } else {
            return None;
        }
    }
    Some((m1, r1))
}

Linear Congruence

給定 $ax \equiv b \pmod m$ 的 $a, b, m$,求解 $x$。

如果 $b$ 不是 $g = gcd(a, m)$ 的倍數,則無解。

我們證他的反向,即有解時,$b$ 一定是 $g$ 的倍數。 有解代表存在 $x_0$ 使得 $a x_0 \equiv b \pmod m$,即存在整數 $y$ 滿足 $a x_0 + my = b$。 因為 $a$ 與 $m$ 都是 $g$ 的倍數,所以 $b$ 一定是 $g$ 的倍數。

如果 $b$ 是 $g = gcd(a, m)$ 的倍數,則解為 $x \equiv \left(\frac{a}{g}\right)^{-1} \frac{b}{g} \pmod{\frac{m}{g}}$

因為 $ax \equiv b \pmod m$ 中,$a, m$ 可能不互質,所以 $a$ 在 $m$ 下的反元素不一定存在,我們將等式兩側除上 $g$ 使得 $a/g$ 與 $m/g$ 互質:

$$ \begin{align} a &x \equiv b \pmod m \\ a &x + my = b \\ \frac{a}{g} &x + \frac{m}{g} y = \frac{b}{g} \\ \frac{a}{g} &x \equiv \frac{b}{g} \pmod {\frac{m}{g}} \\ &x \equiv \left(\frac{a}{g}\right)^{-1} \frac{b}{g} \pmod{\frac{m}{g}} \end{align} $$

fn linear_congruence(a: i64, b: i64, m: i64) -> Option<i64> {
    let g = gcd(a, m);
    if b.rem_euclid(g) != 0 {
        None
    } else {
        Some((minv(a / g, m / g) * (b / g)).rem_euclid(m / g))
    }
}

Extgcd

給定 $ax + by = gcd(a, b)$ 中的 $(a, b)$,extgcd 求出一組解 $(x, y)$,其中 $a, b, x, y$ 都可能是負數。

根據 Bézout's identity,兩數的 gcd 可以寫成兩數的線性組合。於是 Euclidean algorithm 可以寫成:

$$ \begin{align} g &= a \cdot x_0 + b \cdot y_0 \tag{1} \\ g &= b \cdot x_1 + (a \bmod b) \cdot y_1 \tag{2} \\ &\dots \\ g &= g \cdot 1 + 0 \cdot 0 \tag{3} \end{align} $$

因為式 $(3)$ 中的 $(x, y)$ 是已知的,我們想從式 $(3)$ 倒著推,推出式 $(1)$。假設式 $(2)$ 是已知的,我們可以將之寫成式 $(1)$ 的型式:

$$ \begin{align} g &= b \cdot x_1 + (a \bmod b) \cdot y_1 \\ g &= b \cdot x_1 + (a - \lfloor\frac{a}{b}\rfloor \cdot b) \cdot y_1 \\ g &= a y_1 + b \cdot \left( x_1 - y_1 \cdot \lfloor\frac{a}{b}\rfloor \right) \end{align} $$

$$ \begin{align} x_0 &= y_1 \\ y_0 &= x_1 - y_1 \cdot \lfloor\frac{a}{b}\rfloor \end{align} $$

於是 code 可以寫成:

fn extgcd(a: i64, b: i64) -> (i64, i64, i64) {
    if b == 0 {
        (1, 0, a) // (x0, y0, g)
    } else {
        let (x1, y1, g) = extgcd(b, a.rem_euclid(b));
        (y1, x1 - y1 * (a / b), g) // (x0, y0, g)
    }
}

Mod Inverse

如果 $a, m$ 互質,則 extgcd(a, m) 可以求出 $a$ 在 $\pmod m$ 下的反元素,因為 extgcd 可以求出

$$ \begin{align} ax + my &= gcd(a, m) = 1 \\ ax &= -my + 1 \\ ax &\equiv 1 \pmod m \end{align} $$

的一組解 $(x_0, y_0)$,我們再將 $x_0$ 移至 $\mod m$ 底下即可。

如果 $a, m$ 不互質,則 extgcd(a, m) 求出來的是 $\frac a g$ 在 $\pmod {\frac{m}{g}}$ 下的反元素。

Reference