扩展欧几里得算法

求解 \(ax+by = gcd(a,b)\)

首先,如果有 \(b\) 等于 \(0\),则 \(x=1, \ y=0\) 为其一组解(辗转相除法最后一层)。

现在要从底往上推 \(x,y\),设底下一层的 \(x,y\)\(x',y'\)

根据辗转相除法,设 \(a'=b, \ b'=a \bmod b = a - \left\lfloor \dfrac{a}{b} \right\rfloor \cdot b\),则有 \(ax+by = a'x' + b'y'= bx' + (a - \left\lfloor \dfrac{a}{b} \right\rfloor \cdot b)y' = ay' + b(x' - \left\lfloor \dfrac{a}{b} \right\rfloor \cdot y')\)。所以 \(x = y', \ y = x' - \left\lfloor \dfrac{a}{b} \right\rfloor \cdot y'\)

ll exgcd(ll a, ll b, ll &x, ll &y) {
	if (b == 0) {
    	x = 1; y = 0;
		return a;
	}
	ll g = exgcd(b, a % b, x, y);
	ll t = x;
	x = y;
	y = t - (a / b) * y;
	return g;
}

这里函数的返回值是最大公约数,而求出的 \(|x| \le |b|, \ |y| \le |a|\)。时间复杂度为 \(O(\log \max(a,b))\)

显然求出来的是其中一组解,也叫特解。

因为这个方程是 \(ax+by = gcd(a,b)\),所以当 \(x\) 增加 \(\dfrac{b}{gcd(a,b)}\) 的时候,\(y\) 只需要减少 \(\dfrac{a}{gcd(a,b)}\),就能保证 \(ax+by\) 不变,因此把 \(x\)\(\dfrac{b}{gcd(a,b)}\) 取模并调成非负以后就可以得到最小非负整数解,还可以用类似思路得到最小正整数解。

\(a,b\) 是正整数的情况下,\(x\) 越大 \(y\) 越小,\(x\) 越小 \(y\) 越大。

再看方程 \(ax+by=d\),其中 \(d\)\(gcd(a,b)\) 的倍数。

此时,可以先求出 \(ax+by=gcd(a,b)\) 的一组解 \(x,y\)

\(x,y\) 分别乘 \(\dfrac{d}{gcd(a,b)}\),这肯定是原方程 \(ax+by=d\) 的一组解。

如果要求 \(x\) 的最小非负整数解,就对 \(\dfrac{b}{gcd(a,b)}\) 取模。

另外,如果 \(d\) 不是 \(gcd(a,b)\) 的倍数,显然 \(ax+by=d\) 无整数解。


习题:P1082 [NOIP 2012 提高组] 同余方程

解题思路

求同余方程 \(ax \equiv c \pmod {b}\) 的解等价于求 \(ax+bk=c\) 的解 \(x\)

因此本题就是求 \(ax+bk=1\),题目保证有解就是保证了 \(a,b\) 互质。

使用扩展欧几里得算法求解即可。

参考代码
#include <cstdio>
using ll = long long;
ll exgcd(ll a, ll b, ll &x, ll &y) {
    if (b == 0) {
        x = 1; y = 0; return a;
    }
    ll g = exgcd(b, a % b, x, y);
    ll t = x; x = y; y = t - a / b * y;
    return g;
}
int main()
{
    int a, b; scanf("%d%d", &a, &b);
    ll x, y;
    ll g = exgcd(a, b, x, y);
    x = (x % b + b) % b;
    printf("%lld\n", x);
    return 0;
}

习题:P5656 【模板】二元一次不定方程 (exgcd)

解题思路

需要注意判无解,以及要求的是最小正整数解。

正整数解的数量,可以先求一组特解 \(x_0,y_0\),此时考虑调整法,即 \(x_0 + k \cdot \dfrac{b}{gcd(a,b)} > 0\),解这个不等式得到 \(k\) 的下界,同理可根据 \(y_0 - k \cdot \dfrac{a}{gcd(a,b)} > 0\) 解出 \(k\) 的上界。

参考代码
#include <cstdio>
#include <algorithm>
using ll = long long;
using std::max;
ll exgcd(ll a, ll b, ll &x, ll &y) {
    if (b == 0) {
        x = 1; y = 0; return a;
    }
    ll g = exgcd(b, a % b, x, y);
    ll t = x; 
    x = y; y = t - a / b * y;
    return g;
}
ll floor_div(ll a, ll b) {
    ll res = a / b;
    if (a % b != 0 && (a > 0) != (b > 0)) {
        res--;
    }
    return res;
}
ll ceil_div(ll a, ll b) {
    ll res = a / b;
    if (a % b != 0 && (a > 0) == (b > 0)) {
        res++;
    }
    return res;
}
void solve() {
    int a, b, c; scanf("%d%d%d", &a, &b, &c);
    ll x0, y0;
    ll g = exgcd(a, b, x0, y0);
    if (c % g != 0) { // 如果c不能被gcd(a,b)整除,则无整数解
        printf("-1\n"); return;
    }
    // 计算ax+by=c的一个特解
    x0 = x0 * c / g; y0 = y0 * c / g;
    // 定义通解中的周期项
    ll a_prime = a / g, b_prime = b / g;
    // 求解k的范围,使得x>0和y>0
    // x = x0 + k*b_prime > 0 => k > -x0/b_prime
    // y = y0 - k*a_prime > 0 => k < y0/a_prime
    ll k_min = floor_div(-x0, b_prime) + 1;
    ll k_max = ceil_div(y0, a_prime) - 1;
    // 如果k的范围无效,则无正整数解
    ll cnt = max(k_max - k_min + 1, 0ll);
    ll x_min = x0 + k_min * b_prime;
    ll y_min = y0 - k_max * a_prime;
    
    if (cnt > 0) printf("%lld ", cnt);
    printf("%lld %lld", x_min, y_min);
    
    if (cnt > 0) {
        ll x_max = x0 + k_max * b_prime;
        ll y_max = y0 - k_min * a_prime;
        printf(" %lld %lld\n", x_max, y_max);
    } else printf("\n");
}
int main()
{
    int t; scanf("%d", &t);
    for (int i = 1; i <= t; i++) solve();
    return 0;
}

习题:P1516 青蛙的约会

解题思路

列方程 \(x + t \cdot m \equiv y + t \cdot n \pmod {L}\),相当于 \(x + t \cdot m - (y + t \cdot n) = k \cdot L\),整理得到 \((n-m) \cdot t + L \cdot k = x - y\)

使用扩展欧几里得算法求解该方程即可。

注意因为 \(n-m\) 有可能是负的,导致求 gcd 时返回值是负的,所以把解调整成最小正整数解时要对模数取绝对值,保证模数是正的,避免模负数出现问题。

参考代码
#include <cstdio>
using ll = long long;
ll exgcd(ll a, ll b, ll &x, ll &y) {
    if (b == 0) {
        x = 1; y = 0; return a;
    }
    ll g = exgcd(b, a % b, x, y);
    ll t = x; x = y; y = t - a / b * y;
    return g;
}
int main()
{
    int x, y, m, n, l;
    scanf("%d%d%d%d%d", &x, &y, &m, &n, &l);
    int a = n - m, b = l, c = x - y;
    // 特殊情况:n-m=0
    if (a == 0) {
        if (c % b == 0) {
            printf("%d\n", c / b);
        } else printf("Impossible\n");
    } else {
        ll t0, k0;
        ll g = exgcd(a, b, t0, k0);
        // 判断是否有解
        if (c % g != 0) {
            printf("Impossible\n");
        } else {
            // 求一个特解t0,k0
            t0 = t0 * ll(c) / g;
            // 通解为:
            // t = t0 + (b/g) * p
            // k = k0 - (a/g) * p
            // 其中p是任意整数
            // 找到满足 t >= 0 的最小的 t
            // 需要找到 t = t0 + (b/g) * p >= 0
            // t 必须是 t0 模 (b/g) 的同余类 
            ll mod_val = b / g;
            if (mod_val < 0) mod_val = -mod_val;
            ll t = (t0 % mod_val + mod_val) % mod_val;
            printf("%lld\n", t);
        }
    }
    return 0;
}
posted @ 2025-07-10 20:56  RonChen  阅读(56)  评论(0)    收藏  举报