跳转至

扩展欧几里得算法

需求:求 \(ax + by = gcd(a, b)\) 的一组可行解。


  • \(ax_1 + by_1 = gcd(a, b)\)
  • \(bx_2 + (a\ \%\ b)y_2 = gcd(b, a\ \%\ b)\)
  • 因为 \(gcd(b, a\ \%\ b) = gcd(a, b)\),所以有:

    \(ax_1 + by_1 = bx_2 + (a\ \%\ b)y_2\)

  • 因为 \(a\ \%\ b = a - ⌊\frac{a}{b}⌋b\),所以:

    \(ax_1 + by_1 = bx_2 + (a\ \%\ b)y_2 = bx_2 + (a - ⌊\frac{a}{b}⌋b)y_2\)

    \(ax_1 + by_1 = ay_2 + bx_2 - ⌊\frac{a}{b}⌋by_2\)

    \(ax_1 + by_1 = ay_2 + b(x_2 - ⌊\frac{a}{b}⌋y_2)\)

  • 由于 \(a = a,b = b\),所以:

    \(x_1 = y_2,\ y_1 = x_2 - ⌊\frac{a}{b}⌋y_2\)


上面的公式其实是一条向下递推公式,要求 \(x_1, y_1\),就要先求 \(x_2, y_2\),要求 \(x_2, y_2\),就要先求 \(x_3, y_3\),··· 依此类推。

若考虑递推的底部是:

\(1 \cdot gcd(a, b) + s \cdot 0 = gcd(a, b)\)

Warning

若能这样到达递推的底部出现 0 的话,必须要求 \(ax + by = gcd(a, b)\)\(a > b\)

底部的上面一层是:

\((k + 1)gcd(a, b) + (-k)gcd(a, b) = gcd(a, b)\)

若要满足:\(x_1 = y_2,\ y_1 = x_2 - ⌊\frac{a}{b}⌋y_2\),则:

\((k + 1) = s,\ -k = 1 - ⌊\frac{(k + 1)gcd(a, b)}{(-k)gcd(a, b)}⌋s\)

\((k + 1) = s,\ -k = 1 + s\)

\(s = (k + 1) = -(k + 1)\)

解得:k = -1, s = 0


我们可以借助求 \(gcd(a, b)\) 的求法到达递推的底部:

gcd(a, b) 的求法
typedef long long LL;

LL gcd(LL a, LL b)
{
    if (!a || !b) return a | b;
    return a > b ? gcd(b, a % b) : gcd(a, b % a);
}

到达 \(gcd(a, b)\) 的底部时,为 0 端的 \(y = 0\),非 0 端的 \(x = 1\),递归回溯,再确定上面一层:

求 ax + by = gcd(a, b)
// 向下递推的过程保证 a > b
LL exgcd(LL a, LL b, LL &x1, LL &y1)
{
    if (!b)
    {
        x1 = 1; y1 = 0; return a;
    }
    LL x2, y2, gd;
    gd = exgcd(b, a % b, x2, y2);
    x1 = y2; y1 = x2 - a / b * y2;
    return gd;
}

有了这个之后,如果我们要求 \(ax + by = c\) 的一对整数解,由于 \(d\ |\ a, d\ |\ b\) 所以 \(d\ |\ (ax + by)\),即:

\(d\ |\ c\)

我们不妨先求 \(ax + by = gcd(a, b)\) 的一对整数解,然后对该方程左右同时乘上 \(c / gcd(a, b)\) 就可以转换成方程:

\(ac / gcd(a, b)x + bc / gcd(a, b)y = c\)

\(aX + bY = c\) 的解为:\(X = c / gcd(a, b)x,Y = c / gcd(a, b)y\)

如果 \(c / gcd(a, b)\) 不是整数的话,就一定没有整数解,因为上面推导的时候,若有整数解就一定有 \(gcd(a, b)\ |\ c\),它们的逻辑关系是:

有整数解 \(\rightarrow gcd(a, b)\ |\ c\)


「AcWing 877. 扩展欧几里得算法」

给点 \(n\) 对正整数 \(a_i, b_i\),对于每队数,求出一组 \(x_i, y_i\),使其满足 \(a_i \times x_i + b_i \times y_i = gcd(a_i, b_i)\)

代码参考
#include <bits/stdc++.h>

using namespace std;
int debuggersum = 0;

// 即使是不成熟的尝试,

typedef long long LL;

// 向下递推的过程保证 a > b
LL exgcd(LL a, LL b, LL &x1, LL &y1)
{
    if (!b)
    {
        x1 = 1; y1 = 0; return a;
    }
    LL x2, y2, gd;
    gd = exgcd(b, a % b, x2, y2);
    x1 = y2; y1 = x2 - a / b * y2;
    return gd;
}

void solve(void)
{
    int n; scanf ("%d", &n);
    while (n --)
    {
        LL a, b; scanf ("%lld%lld", &a, &b);
        LL x, y; 
        if (a >= b)
        {
            exgcd(a, b, x, y);
            printf ("%lld %lld\n", x, y);
        }
        else
        {
            exgcd(b, a, y, x);
            printf ("%lld %lld\n", x, y);
        }
    }
}

// 也胜于胎死腹中的策略。

int main(void)
{
    ifstream fi; ofstream fo;
    fi.open("./lrq.in"); fo.open("./lrq.out");
    if (fi.is_open() && fo.is_open())
    {
        fo << "start running ..." << endl; fo.close(); fi.close();
        for (long long i = 1; i <= 4e8 + 2e7; i++);
        FILE *fin = freopen("./lrq.in", "r", stdin);
        FILE *fout = freopen("./lrq.out", "w", stdout);
        solve(); fclose(fin); fclose(fout);
    }
    else solve();

    return 0;
}