【2024.9.28练习】青蛙的约会

题目描述


题目分析

        由于两只青蛙都在跳跃导致变量多,不妨采用物理题中的相对运动思想,设青蛙A不动,青蛙B每次跳n-m米,两只青蛙的距离为x-y米。正常来说,只要模拟青蛙B与青蛙A的相对运动过程,最终当青蛙B与青蛙A距离为0时即可求得总跳跃次数。但是难点在于数据量大,按部就班的模拟将会超时。

        由于这是一道有关整数和环的数学题,考虑使用扩展欧几里得算法求解。现直接假设一只青蛙静止,另一只青蛙每次位移为a=n-m,总跳跃次数为X,一圈长度为b=L,跳跃的圈数为Y,青蛙间的距离为c=x-y。则题目满足方程:aX+bY=c。显然要使方程有整数解,必须满足的条件是:c必须是 a 和 b 的最大公约数的倍数,即ax\equiv c(mod\ b)。这可以通过贝祖定理证明。

        接下来的难点是求出最小跳跃次数。根据拓展欧几里得算法已经得到了方程的一组特解,但这不一定是最优解,根据线性同余方程的通解公式:

x=x_0+k\frac{b}{d}\mid k\in \mathbb{Z},d=gcd(a,b)

只需让该特解模除\frac{b}{d}即可求出跳跃次数在(0,\frac{b}{d}]间的最小正整数解。

        提交后有一处测试点有误,经检查,原来在exgcd算法中,如果两个参数存在负数,输出的最大公因数gcd(a,b)也可能是负数。这并不影响所得特解的正确性,但在求最小正解的时候,需要采取措施让取模后的结果转变为正数。


我的代码

#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;

// 扩展欧几里得算法
ll extgcd(ll a, ll b, ll& x, ll& y) {
    if (b == 0) {
        x = 1;
        y = 0;
        return a;
    }
    ll d = extgcd(b, a % b, y, x);
    y -= (a / b) * x;
    return d;
}

int main() {
    ll x, y, m, n, L;
    cin >> x >> y >> m >> n >> L;

    ll a = n - m;  // 移项后得到 a = n - m
    ll b = x - y;  // 常数项 b = x - y
    ll l = L;      // 模数为 L

    ll x0, y0;
    ll d = extgcd(a, l, x0, y0);  // 使用扩展欧几里得求解
    if (b % d != 0) {
        cout << "Impossible" << endl;
    }
    else {
        // 存在解的情况,求最小非负整数解
        ll k = (x0 * (b / d)) % (l / d); //其中x0*(b/d)为一个方程的特解
        if (k < 0) {
            if (l / d > 0) {
                k += l / d;  // 保证 k 为非负
            }
            else {
                k -= l / d;  // 保证 k 为非负
            }
        }
        cout << k << endl;
    }

    return 0;
}

由于扩展欧几里得算法参数使用了引用,导致逻辑相对更难理解,我利用原本欧几里得算法的数学递推逻辑修改为一下函数,程序可以成功运行:

ll x_0;
ll y_0;
ll x_1;
ll y_1;
ll extgcd(ll a, ll b) {
    ll d = a;
    if (b != 0) {
        d = extgcd(b, a % b);
        x_0 = y_1;
        y_0 = x_1 - a / b * y_1;
        y_1 = y_0;
        x_1 = x_0;
    }
    else {
        x_1 = 1;
        y_1 = 0;
    }
    return d;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值