青蛙的约会POJ1061
题意:
两只青蛙在网上相识了,它们聊得很开心,于是觉得很有必要见一面。
它们很高兴地发现它们住在同一条纬度线上,于是它们约定各自朝西跳,直到碰面为止。
可是它们出发之前忘记了一件很重要的事情,既没有问清楚对方的特征,也没有约定见面的具体位置。
不过青蛙们都是很乐观的,它们觉得只要一直朝着某个方向跳下去,总能碰到对方的。
但是除非这两只青蛙在同一时间跳到同一点上,不然是永远都不可能碰面的。
为了帮助这两只乐观的青蛙,你被要求写一个程序来判断这两只青蛙是否能够碰面,会在什么时候碰面。
我们把这两只青蛙分别叫做青蛙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”
数据范围
x != y < 2000000000,
0 < m,n <2000000000,
0 < L < 2100000000
输入格式:
1 2 3 4 5
输出格式:
4
前言
数论作为数学的皇冠,在算法竞赛中也是热门知识点。 常见的数论知识点有:欧拉筛
欧拉函数
欧几里得
扩展欧几里得
中国剩余定理
而本题便考察了扩展欧几里得(exgcd)的知识点。
一、什么是扩展欧几里得?
介绍扩展欧几里得不得不提一提欧几里得,也就是常用的求二个整数a,b的最大公约数,即gcd(a,b)=gcd(b,a%b)=…=gcd(x,0)。
1.欧几里得模板
下面代码用于求解,整数a,b的最大公约数gcd(a,b)。
/*注意a>b,如果a<b交换一下位置即可*/
int gcd(int a,int b)
{
/*递归迭代法*/
if(b==0) return a;
else return gcd(b,a%b);
}
int gcd(int a,int b)
{
/*非递归迭代法*/
while(b!=0)
{
int r=a%b;
a=b;
b=r;
}
return a;
}
2.扩展欧几里得模板
1.求解不定方程:ax+by=gcd(a,b)(也称丢番图方程)的解。
int exgcd(int a,int b,int &d,int &x,int &y)//d是a和b的最大公约数x,y是初始解
{
/*递归边界*/
if(b==0)
{
x=1;
y=0;
d=a;
}
else
{
exgcd(b,a%b,d,x,y);
/* x1=y2,y1=x2-(a/b)*y1 扩展欧几里得递归+回溯求初解的公式*/
int t=y;
y=x-(a/b)*y;
x=t;
/*
此处也可写成
exgcd(b,a%b,d,y,x);
y=y-(a/b)*x;
*/
}
return 0;
}
二、扩展欧几里得的简要证明
证明:
步骤一:求方程ax0+by0=gcd(a,b)的特解
一次迭代后:bx0+(a%b)y0=gcd(b,a%b),
由gcd(a,b)=gcd(b,a%b)
得ax0+by0=bx1+(a%b)y1=bx1+(a-[a/b]*b)y1
提右式系数和左式a,b对应得:
即ax0+by0=ay1+b(x1-[a/b])y1
即x0=y1
y0=(x1-[a/b])y1
由此方程ax0+by0=gcd(a,b)的特解就求出来了。
步骤二:ax+by=c方程的解
ax+by=c方程由ax0+by0=gcd(a,b)左右同c/gcd(a,b)得到。
因此ax0c/gcd(a,b)+by0c/gcd(a,b)=c
故 x=x0c/gcd(a,b)
y=y0*c/gcd(a,b)
三、题解
倘若两个青蛙要跳到同一个地方,那么需要满足:
x+q⋅m≡y+q⋅n (mod L)
q⋅(m−n)≡y−x (mod L)
q(m−n)+pL=y−x
q(n−m)+pL=x−y等价于ax+by=c
这样一看就是裸的扩展欧几里得了。
首先证明有解性,令d = gcd(a, b),方程两边除以d得到a/d * x + b/d * y = c/d,由于a是整除d的,b也是整除d的,而x、y都是整数解,故当且仅当c/d时,才有解。否则输出Impossible。
在由上面简要证明求出ax0+by0=d的解x0和y0,继而两边乘以c/d即a(c/d * x0) + b(c/d * y0) = c,就可以得到原来方程的解x = (c/d * x0),y = (c/d * y0)。
四、题解代码
由于是求解跳几次会碰面,即最小的x。
由于方程求解出来的x可能是负数,故第一个大于0的x为代求解。
求出来x可能是负的,
由a(x+kb)+b(y-ka)=gcd(a,b);
那么xo=x+kb/d和yo=y-ka/d都能是该方程的解(d为gcd(a,b))
因为kb/d和ka/d可取的值比kb和ka更多,
所以通解只能是
xo=x+kb/d
yo=y-ka/d
故最小的正x则为(x%b/d+b/d)%b/d;
#include<cstdio>
#define ll long long
using namespace std;
ll d,x0,y0;
ll abs(ll x){return x<0?-x:x;}
ll exgcd(ll a,ll b,ll &x,ll &y){//x,y可以使用全局变量,这样就不用写形参了。
if (b==0){
x=1;y=0;
return a;
}
ll dd=exgcd(b,a%b,x,y);//也可以写成exgcd(b,a%b,y,x),这样只需+个y=y-x*(a/b);即可
ll t=x;x=y;y=t-y*(a/b);
return dd;
}
int main(){
ll a,b,m,n,L;
scanf("%lld%lld%lld%lld%lld",&a,&b,&m,&n,&L);
ll s1=m-n,s2=b-a;
if (s1<0) s1=-s1,s2=-s2;
d=exgcd(s1,L,x0,y0);
if (s2%d!=0) puts("Impossible");
else{
x0*=s2/d;
printf("%lld",(x0%(L/d)+L/d)%(L/d));
}
return 0;
}