简明题意
更好的题面阅读体验,请见 LibreOJ #10209. 「一本通 6.4 例 1」青蛙的约会 。
PS: 部分前置知识请移步 之前在 优快云 写的相关博客 。窃以为题解应当模块化,读者自取想学的知识, 因此其中关于同余方程的部分就不细解释了。
题目分析
不太愉悦地推式子
地球是两极稍扁赤道略鼓的椭球体。 纬线圈可以近似看成圆。
如果不同意上面的说法,可以关闭此篇题解了(滑稽
看完题面,发现是道追及问题,而且是在一个圆上。
先从直线运动的模型来分析,设时间为 $ t (t \in \mathbb{N}^*) $ 那么青蛙 $ A $ 的位移 $ x_a = x + mt$,青蛙 $ B $ 的位移为 $ x_b = y + nt$。当他们相遇时,满足 $ x_a = x_b $ 即 $ x + mt = y + nt $ 。
再转到圆上,我们知道其周长为 $ L $, 若两青蛙相遇,则满足
x a ≡ x b m o d L x_a \equiv x_b \mod L xa≡xbmodL
即:
x + m t ≡ y + n t m o d L x + mt \equiv y + nt \mod L x+mt≡y+ntmodL
移项后有:
t ( m − n ) ≡ y − x m o d L t(m - n) \equiv y - x \mod L t(m−n)≡y−xmodL
氦,最后还是得转化成: $ t(m - n) - (y - x) = kL $ 有整数解
PS: $ t(m - n) $ 和 $ y - x $ 对 $ L $ 取余相等就相当于 $ t(m - n) $ 和 $ (y - x) $ 刚好相差整数个 $ L $。
不希望有负号并且希望式子更加简单,我们再变换一下,也就是 $ kL + t(n - m) = x - y $ 有整数解
划出来这里的未知量: $ t $ 和 $ k $, 不定方程有了,剩下的事就是数学老师的事了。
这时敏锐的你发现这是 A x + B y = C Ax + By = C Ax+By=C 的形式,根据裴蜀定理,当且仅当 $ \gcd(A, B) \mid C $ 有整数解。如果有解,我们可以用扩展欧几里得算法求出 $ A $ 和 $ B $ 的其中一个解。
愉悦地码代码
我们的目标是解这个方程:
k l + t ( n − m ) = x − y kl + t(n - m) = x - y kl+t(n−m)=x−y
第一步: 输入,把数据整理成上述格式。
long long x, y, m, n, l;
scanf("%lld %lld %lld %lld %lld", &x, &y, &m, &n, &l);
long long X = l, Y = n - m, C = x - y; // 自行对比 AX + BY = C
第二步: 处理系数。
if (Y < 0) Y = -Y, C = -C; // 系数必须为正整数
LL g = __gcd(X, Y); // 判断是否有解,化简方程
if (C % g) { printf("Impossible"); return 0; } // 互质,无解
我们不能只背定理不记条件(hhh,反正不用证), 根据题意 $ L $ 一定为正, 而 $ n - m $ 不一定,如果它小于 $ 0 $,则需要把整个方程两边乘 $ -1 $,这里没写 X = -X
是因为解出来只不过是系数正负的区别,不影响。
如果 $ \gcd(A, B) \nmid C $,则方程无符合题意的整数解,输出 Impossible
。
第三步: 解不定方程。
X /= g; Y /= g; C /= g; // 化简
pLL sol = exgcd(X, Y); // 求解
sol.second *= C; // 通过 ... = gcd(x, y) 的解求原方程的解,此时 x, y 已经互质
扩展欧几里得求解的是 $ Ax + By = \gcd(A, B) $ 的解, 最后需要将结果同乘 $ \frac{C}{\gcd(A, B)}$ 才能得到我们需要的答案。这里我们通过同除其最大公因数使其互质,则只需要将结果乘 $ C $ 就行了。
第四步: 处理并输出答案。
printf("%lld", (sol.second % X + X) % X); // 最小正整数解
由于题目让求最小正整数解,而扩展欧几里得过程的结果并不能保证是,因此我们对答案再进行取模(要 $ +X $ 是为了避免负数)。
代码
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long LL;
typedef pair<LL, LL> pLL;
pLL exgcd(LL a, LL b)
{
if(b == 0) return make_pair(1, 0);
pLL t = exgcd(b, a % b);
pLL res;
res.first = t.second;
res.second = t.first - (a / b) * t.second;
return res;
}
int main()
{
LL x, y, m, n, l;
scanf("%lld %lld %lld %lld %lld", &x, &y, &m, &n, &l);
LL X = l, Y = n - m, C = x - y; // 经过化简(或由同余方程推导)kl + t(n - m) = x - y
if (Y < 0) Y = -Y, C = -C; // 系数必须为正整数
LL g = __gcd(X, Y); // 判断是否有解,化简方程
if (C % g) { printf("Impossible"); return 0; } // 互质,无解
X /= g; Y /= g; C /= g; // 化简
pLL sol = exgcd(X, Y); // 求解
sol.second *= C; // 通过 ... = gcd(x, y) 的解求原方程的解,此时 x, y 已经互质
printf("%lld", (sol.second % X + X) % X); // 最小正整数解
return 0;
}
跑的还挺快的,自带 O2
优化的 LibreOJ 只跑了 10ms
。