exgcd 学习笔记

扩展欧几里得算法简称扩欧或 exgcd。它是求解最大公约数的欧几里得算法的扩展,用于求解二元一次不定方程的重要算法,也是数论的重要学习内容之一。

1.1 算法介绍

我们规定 \(a, b> 0\),其他情况类似,读者可自行推导,令 \(d = \gcd(a,b)\)。

在解方程之前,我们先要判定一个方程是否有解,我们可以利用下面的定理:

裴蜀定理:二元一次不定方程 \(ax + by = c\) 有解的充要条件为 \(d \, \vert \, c\)。

若想求解 \(ax+by=c\),我们显然只要求解 \(ax+by=d\),最后再将答案乘上 \(\dfrac{c}{d}\),由裴蜀定理可知这是整数。

回顾求解 \(\gcd(a,b)\) 的方法,我们利用恒等式 \(\gcd(a,b)=\gcd(b, a \bmod b)\) 递归求解,利用这一思想,我们假设我们已经得到了 \(bx'+(a \bmod b)y' = d\) 的一组特解,显然可得:

\[\begin{aligned}ax + by & = bx'+(a \bmod b)y' \\ & = bx'+(a - b \left\lfloor \dfrac{a}{b} \right\rfloor)y' \\ & = ay' + b(x' -\left\lfloor \dfrac{a}{b} \right\rfloor y')\end{aligned} \]

容易得到:\(x = y', y = x' -\left\lfloor \dfrac{a}{b} \right\rfloor y'\),这给了我们很好的性质去递归求解方程的特解,显然当 \(a=1, b=0\) 时,方程的一组解为 \(x = 1, y = 0\),回溯计算即可,设 \(V\) 表示值域大小,时间复杂度为 \(\mathcal{O}(\log V)\),代码实现如下:

inline void exgcd(int a, int b, int &x, int &y) {
    if (!b) return x = 1, y = 0, void();
    exgcd(b, a % b, y, x); y -= a / b * x;
}

1.2 通解的形式和范围

对于一般的题,求出一组特解是远远不够的,所以我们要对通解进行研究,设 exgcd 求出的特解为 \(x_0\) 和 \(y_0\),则对于 \(s \in \mathbb{Z}\),容易发现通解满足下面的式子:

\[x = x_0 + \dfrac{b \times s}{d} \]

\[y = y_0 - \dfrac{a \times s}{d} \]

那么对于正整数解,我们显然可以求出 \(s\) 的范围:

\[x > 0 \iff x_0 + \dfrac{b \times s}{d} > 0 \iff s > -\dfrac{dx_0}{b} \]

\[y > 0 \iff y_0 - \dfrac{a \times s}{d} > 0 \iff s < \dfrac{dy_0}{a} \]

由于 \(s\) 是整数,所以:

\[\left\lceil \dfrac{1 - dx_0}{b} \right\rceil \le s \le \left\lfloor \dfrac{dy_0 - 1}{a} \right\rfloor \]

下面给出 【模板】二元一次不定方程 (exgcd) 的代码实现:

inline void exgcd(int a, int b, int &x, int &y) {
    if (!b) return x = 1, y = 0, void();
    exgcd(b, a % b, y, x); y -= a / b * x;
}
inline void Solve() {
    int a = read(), b = read(), c = read();
    int d = __gcd(a, b);
    if (c % d) printf("-1\n");
    else {
        int x0, y0, T = c / d;
        exgcd(a, b, x0, y0);
        x0 *= T; y0 *= T;
        int smin = floor(1.0 * (b - d * x0) / b), smax = floor(1.0 * (d * y0 - 1) / a);
        if (smin > smax) {
            int xmin = x0 + smin * b / d;
            int ymin = y0 - smax * a / d;
            printf("%lld %lld\n", xmin, ymin);
        }
        else {
            int xmin = x0 + smin * b / d;
            int xmax = x0 + smax * b / d;
            int ymin = y0 - smax * a / d;
            int ymax = y0 - smin * a / d;
            printf("%lld %lld %lld %lld %lld\n", smax - smin + 1, xmin, ymin, xmax, ymax);
        }
    }
}

参考博客:Alex_wei - 初等数论学习笔记 I:同余相关

posted @ 2023-09-24 02:13  WTR2007  阅读(107)  评论(0)    收藏  举报