再来一次基础数论全家桶
约数相关
\(\mathcal{gcd}\)
我100年前的证明自己都已经看不懂了,所以我们这里再浅浅的证明一下。
好,于是就可以用递归求 \(\mathcal{gcd}\) 了。
i64 gcd(i64 a, i64 b) {
return !b ? a : gcd(b, a % b);
}
\(lcm\)
有一个结论 \(lcm(a, b) \times \gcd(a, b) = a \times b\)。
证明
原式就是要证明 \(lcm(a, b) = a \times b \div \gcd(a, b)\)。
设 \(g = \gcd(a, b),a_0 = a / g, b_0 = b / g\),因为 \(g\) 是 \(a\) 和 \(b\) 的最大公约数,所以 \(a_0\) 和 \(b_0\) 显然是互质的。
则 \(lcm(a_0, b_0) = a_0 \times b_0\)。
所以 \(lcm(a, b) = lcm(a_0 \times g, b_0 \times g) = lcm(a_0, b_0) \times g = a_0 \times b_0 \times g = a \times b_0 = a \times b \div g\)。
证毕。
\(\mathcal{exgcd}\)
balbabalba定理(?):\(\forall a, b\),存在一对 \(x, y \in \mathbb{N}\),满足 \(ax + by = \gcd(a, b)\).
奇怪的证明
分类讨论:
- 当 \(b = 0\) 时,显然此时 \(x = 1, y = 0\) 满足上面的式子。
- 当 \(b < 0\) 时,则有 \(\gcd(a, b) = \gcd(b, a \mod b)\),即有 \(ax + by = bx + (a \mod b) \times y=bx+(a-\lfloor a/b\rfloor\times b)y=ay+b\times (x-\lfloor a/b\rfloor\times y)\)
设 \(x_0 = y, y_0 = x-\lfloor a/b\rfloor\times y\),就是有 \(a\times x_0 + b\times y_0 = \gcd(a, b)\),欧几里得算法的递归过程应用数学归纳法,可以知道这个定理时成立的,同时也可以再求 \(\gcd\) 的时候递归同时求出。
void exgcd(int a, int b, int &x, int &y) {
if (!b) {
x = 1, y = 0;
return;
}
exgcd(b, b % a, x, y);
int z = x;
x = y;
y = z - (a / b) * x;
}
关于它的解
当 c 是任意数时
这里的我们要解决 \(ax + by = c\) 的不定方程, \(c\) 可以是任意的一个非 0 数。
这里设 \(g = \gcd(a, b)\)
转化一下就可以得到:\(\frac{g}{c}ax + \frac{g}{c}by = g\)
显然,当 \(g\mid c\) 时,原方程有整数解,用 \(exgcd\)即可求解。
通解
我们用 \(exgcd\) 求出的是这个方程的特解,对于这个不定方程,它是有无数个解的,我们可以这样表示它的通解。
显然是成立的,如果你不能理解,就可以把这个 \(x\) 和 \(y\) 带回原来的方程,可以发现两个含有 \(k\) 的部分抵消了。
一个简答的模板题
luogu P5656 【模板】二元一次不定方程 (exgcd)。
求一个不定方程的正整数解的个数, \(x, y\) 的可能的最大和最小的正整数解,或者报告无解。
无解的情况很好判断,不在赘述。
我们求完这个方程的一组特解后, \(x\) 和 \(y\) 都又可能小于 \(0\),所以我们可以加入如下操作是他们一定是正数。
由上面的式子可以知道,它们的变化是相反的,如果我们保证了 \(x\) 大于 \(0\) 保证不了 \(y\) 大于 \(0\),那他就是没有正整数解的。
这里为了保证一定是大于 \(0\) 的,我们要用 \(1-x\) 来做上取整。
if (x <= 0) {
i64 k = ceil((1.0 - x) / b);
x += k * b;
y -= k * a;
if (y <= 0) {
y += ceil((1.0 - y) / a) * a;
printf("%lld %lld\n", x, y);
continue;
}
}
\(y\) 也是类似的。
现在我们的 \(x,y\) 都一定是正数了。
然后就比较的简单了,对于正整数解的个数,就是 \(x, y\) 中能操作的最多的次数,及 max(ceil(1. * x / b), ceil(1. * y / a))
。
对于最小的正整数,我们就秉持能减就减的原则,x - floor((x - 1.) / b) * b
就是 \(x\) 的最小正整数解,同时可以注意到,为了保证一定是一个正数,我这里对 \(x\) 进行了减 1,这样才能保证,不然就要特判他是不是 0,如果是 0 那最小的整数解就应该是 \(b\),\(y\) 也是类似的就不说了。
对于最大的正整数解,对于 \(x\) 来说,如果 \(y\) 的解是最小的, \(x\) 的解就是最大的,既然我们已经知道了 \(y\) 的最小正整数解,那么直接带入原方程就可以算出 \(x\) 的最大解, \(y\) 同理。
#define moretest int ___; for (scanf("%d", &___); ___; ___--)
void exgcd(i64 a, i64 b, i64 &x, i64 &y) {
if (!b) {
x = 1, y = 0;
return;
}
exgcd(b, a % b, x, y);
i64 z = x;
x = y;
y = z - (a / b) * x;
}
int main() {
moretest {
i64 a, b, c;
read(a, b, c);
i64 g = gcd(a, b);
if (c % g) {
puts("-1");
continue;
}
i64 x, y;
a /= g, b /= g, c /= g;
exgcd(a, b, x, y);
x *= c, y *= c;
if (x <= 0) {
i64 k = ceil((1.0 - x) / b);
x += k * b;
y -= k * a;
if (y <= 0) {
y += ceil((1.0 - y) / a) * a;
printf("%lld %lld\n", x, y);
continue;
}
}
if (y <= 0) {
i64 k = ceil((1.0 - y) / a);
x -= k * b;
y += k * a;
if (x <= 0) {
x += ceil((1.0 - x) / b) * b;
printf("%lld %lld\n", x, y);
continue;
}
}
// printf("a = %lld b = %lld x = %lld y = %lld\n", a, b, x, y);
vector<i64> ans;
ans.pb(max(ceil(1. * x / b), ceil(1. * y / a)));
// x 可能的最小值
i64 X = x - floor((x - 1.) / b) * b;
// y 可能的最小值
i64 Y = y - floor((y - 1.) / a) * a;
ans.pb(X);
ans.pb(Y);
ans.pb((c - Y * b) / a);
ans.pb((c - X * a) / b);
for (auto v : ans) printf("%lld ", v);
puts("");
}
return 0;
}