POJ 1061 青蛙的约会

1 扩展欧几里德算法

扩展欧几里德算法是用来在已知a, b求解一组x,y,使它们满足贝祖等式: ax+by=gcd(a,b)=d (解一定存在,根据数论中的相关定理)。扩展欧几里德常用在求解模线性方程及方程组中。
gcd: 最大公约数 greatest common divisor

关键点 gcd(a,b)=gcd(b,amodb)
假设 a=kb+r , d 是 a, b的公约数
那么 因为 r=kba 所以r|d , 因此 d也是(b,r)即(b,a mod b)的公约数。
反向证明同上。

// gcd 求解代码
// ax + by = d
int x, y, d;

void gcd(int a, int b) {
    if (b == 0) { 
        x = 1; y = 0; d = a;
        return;
    }

    gcd(b, a%b);

    int temp = x;
    x = y;
    y = temp - a/b*y;
    return;
}

关于迭代的x,y变换

ax+by=d=xb+y(amodb)

a=kb+r

ax+by=ya+(xyk)b

一一对应,

x=y,y=xyk=xya/b

2 青蛙的约会

两只青蛙在网上相识了,它们聊得很开心,于是觉得很有必要见一面。它们很高兴地发现它们住在同一条纬度线上,于是它们约定各自朝西跳,直到碰面为止。可是它们出发之前忘记了一件很重要的事情,既没有问清楚对方的特征,也没有约定见面的具体位置。不过青蛙们都是很乐观的,它们觉得只要一直朝着某个方向跳下去,总能碰到对方的。但是除非这两只青蛙在同一时间跳到同一点上,不然是永远都不可能碰面的。为了帮助这两只乐观的青蛙,你被要求写一个程序来判断这两只青蛙是否能够碰面,会在什么时候碰面。 (可见网聊是多么不靠谱啊,可怜的小青蛙)
我们把这两只青蛙分别叫做青蛙A和青蛙B,并且规定纬度线上东经0度处为原点,由东往西为正方向,单位长度1米,这样我们就得到了一条首尾相接的数轴。设青蛙A的出发点坐标是x,青蛙B的出发点坐标是y。青蛙A一次能跳m米,青蛙B一次能跳n米,两只青蛙跳一次所花费的时间相同。纬度线总长L米。现在要你求出它们跳了几次以后才会碰面。

输入

输入只包括一行5个整数x,y,m,n,L,其中x≠y < 2000000000,0 < m、n < 2000000000,0 < L < 2100000000。

输出

输出碰面所需要的跳跃次数,如果永远不可能碰面则输出一行”Impossible”


思路

在两只青蛙碰见时会有

k(mn)+αL=yx

其中 k 为跳的次数 α 为整数。

x=k,a=mn,y=α,b=L,D=yx 可得
ax+by=D
注意,此处D不一定是a,b的公约数

下面这段推导参考了圣剑的博客
不过没关系,先用扩展欧几里得得出 ax+by=gcd(a,b) 的一个解 x0,y0 ;再观察两个式子:

ax+by=D
ax0+by0=d (令 d=gcd(a,b) )

将上面的式子两边都除以 d ,得:
xa/d+yb/d=D/d
因为d为a,b的最大公约数,所以a/d,b/d为整数,所以D/d必定为整数,否则方程无解;
将下面式两边都乘以d/D与上式比较:

ax+by=D
ax0D/d+by0D/d=D

x=x0D/d,y=y0D/d


扩展解

对于方程 ax0/d+by0/d=1
因为d是a,b的gcd, 所以a/d b/d 互质,那么若有

u(b/d)(a/d)+v(a/d)(b/d)=0

(x0+u(b/d))(a/d)+(y0+v(a/d))(b/d)=1

故而方程 ax/d+by/d=1 的解为

x=x0+u(b/d)

由于 x=x0D/d,y=y0D/d
则本题的解 k=x=(x0+u(b/d))(D/d)
故而其最小解为 k=(x0D/d)mod(b/d)

3 C++实现

//
//  main.cpp
//  POJ_1061_青蛙的约会
//
//  Created by 黄成林 on 16/3/24.
//  Copyright © 2016年 黄成林. All rights reserved.
//

/**
 * url: http://poj.org/problem?id=1061
 */

#include <cstdio>

long long x0, y0, d;

void gcd(long long a, long long b) {
    if (b == 0) {
        x0 = 1; y0 = 0; d = a;
        return;
    }

    gcd(b, a%b);

    long long temp = x0;
    x0 = y0;
    y0 = temp - a/b*y0;
    return;
}

int main(int argc, const char * argv[]) {
    long long x, y, m, n, L;
    while (scanf("%lld %lld %lld %lld %lld", &x, &y, &m, &n, &L) != EOF) {     
        long long a = m - n;
        long long b = L;
        long long D = y - x;
        if (a < 0) { a = -a; D = -D; }

        gcd(a, b);

        if (0 != D%d) {
            printf("Impossible");
        } else {
            long long k = x0 * D / d;
            long long mod = L / d;
            if (k >= 0) {
                k %= mod;
            } else {
                k = k%mod + mod;
            }
            printf("%lld", k);
        }
    }

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值