扩展欧几里得算法
求解 \(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;
}

浙公网安备 33010602011771号