轨迹 发表于 2009-12-2 21:38

前几天参加比赛的一道小编程题

前几天参加了一个编程比赛,有下面这么一小题,当时写了一个算法,能运算出结果,但是运算量相当大,所有数据必须声明为long int类型。觉得还是蛮有意思的一道题,所以放上来让大家一起想想办法。
本人的思路是:
(1)首先算出A青蛙和B青蛙各跳多少次以后能分别回到初始位置。
(2)然后求出这两个次数的最小公倍数,那就是他们同时回到初始位置所需要跳跃的最小次数n。
(3)此时可进行位移的等式判定,如果在上面求出的n次之内,他们都没有碰面的话,那就说明他们永远碰不了面,输出impossible。

回来查了下网上有些算法,都是把n定义为10万或是100万甚至更多。个人认为这是不严谨的,因为你无法证明,他们在跳第10万零一次时不会相遇。不知道大家都有些什么好想法呢~~

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


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

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

轨迹 发表于 2009-12-3 22:01

都一天啦……没人有算法么……:(eew

zapline 发表于 2009-12-3 22:20

我做不出
:lol不过网上有的

轨迹 发表于 2009-12-3 22:55

我做不出
:lol不过网上有的
zapline 发表于 2009-12-3 22:20 http://www.52pojie.cn/images/common/back.gif
网上我查过了,都是给n一个预定义的数,这样肯定是不行的,L的取值范围可以到21亿的单位米,如果L取21亿米,m,n取1米和2米的话,或者0.1米,0.2米的话,n的大小是无法确定的。惆怅啊~~曾经想过采用反证的方法,假设他们能在某点碰头,然后计算他们是否能回到初始位置,可惜算法设计失败……

zapline 发表于 2009-12-3 23:02

PKU1061 解题报告 青蛙的约会 __用扩展欧几里得解模同余方程 收藏
题目描述:

两只青蛙在网上相识了,它们聊得很开心,于是觉得很有必要见一面。它们很高兴地发现它们住在同一条纬度线上,于是它们约定各自朝西跳,直到碰面为止。可是它们出发之前忘记了一件很重要的事情,既没有问清楚对方的特征,也没有约定见面的具体位置。不过青蛙们都是很乐观的,它们觉得只要一直朝着某个方向跳下去,总能碰到对方的。但是除非这两只青蛙在同一时间跳到同一点上,不然是永远都不可能碰面的。为了帮助这两只乐观的青蛙,你被要求写一个程序来判断这两只青蛙是否能够碰面,会在什么时候碰面。
我们把这两只青蛙分别叫做青蛙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"

Sample Input:

1 2 3 4 5

Sample Output:

4




题目分析:

   根据题意可知,题目要求的是求(x+k*m)mod l=(y+k*n)mod l这个模方程的k的最小整数解;跟据模的性质对这个方程变形得:k*(m-n)+t*l=y-x(t为引进的一整数);

令a=(m-n);b=y-x;得:

    k*a+t*l=b; 0)

看到这个式子是不是感觉很眼熟,对,它就是扩展欧几里得法能解的标准方程a*x+b*y=d(此a,b非彼a,b)的标准形;唯一的不同是扩展欧几里得d是前面两系数a,b的最大公约数;而0)式中的b就不能保证了。

不过没关系,先用扩展欧几里得得出k*a+t*l=gcd(k,l)的一个解k0,t0;再观察两个式子:

                        k*a+t*l=b 0)

                     k0*a+t0*l=d 1)(令d=gcd(k,l)));

将0)式两边都除以d,得:

          k*(a/d)+t*(l/d)=b/d;

因为d为a,b的最大公约数,所以a/d,l/d为整数,所以b/d必定为整数,否则方程无解;

将1)式两边都乘以b/d与0)式比较:

      k*a+t*l=b0)

   (k0*b/d)*a+t0*(b/d)*t=b 1)

得k=k0*b/d;t=t0*b/d;

接下来就是由一个解扩展成一个解系:

       1)除以d得: k0*(a/d)+t0*(l/d)=1;

                           因为(a/d)和(l/d)互质,将方程写成:

                                       (k0+u*(l/d))*(a/d)+(t0+v*(a/d))*(l/d)=1;

                            只要u*(l/d)*(a/d)+v*(a/d)*(l/d)=0成立就行;

所以k,t的解系可以写成k=(k0+u*(l/d))*(b/d);t=(t0+v*(a/d))*b/d;

接下来就是求k的最小整数解了;

因为k=(k0 mod (l/d)+(u+k0/(l/d))*(l/d))*(b/d);

所以kmin=(k0*(b/d)) mod (l/d);

大功基本高成了,还差最后一步,那就是如何用扩展欧几里得解k*a+t*l=gcd(k,l):

对于a*x+b*y=d;
递归调用时
令a=b;b=a%b;
将其变为形式2) b*x+a%b*y=d;
         变形:b*x+a*y-(a/b)*b*y=c;
         再变:a*y+b(x-a/b*y)=c; 3)
      与2)式比较:b*x+a%b*y=c;   2)
得:
   当a=b;b=a%b时:x=y;y=x-a/b*y;

调用过程中的x,y就是对应的a,b的解;
当回到顶层时,a,b就是最初的a,b所以此时的x,y就是所求解;
函数写法如下:(解保存在全局变量k,t中)
__int64 extended_gcd(__int64 a,__int64 b)
{
if (b==0)
{
   k=1;
   t=0;
   d=a;
   return a;

}
else
{
    __int64 tp_gcd;
    tp_gcd=extended_gcd(b,a%b);
    __int64 temp;
    temp=k;
    k=t;
    t=temp-(a/b)*t;
    return tp_gcd;
}
}
附上AC代码:
#include<stdio.h>
__int64 k,t,d;
__int64 extended_gcd(__int64 a,__int64 b)
{
if (b==0)
{
   k=1;
   t=0;
   d=a;
   return a;

}
else
{
    __int64 tp_gcd;
    tp_gcd=extended_gcd(b,a%b);
    __int64 temp;
    temp=k;
    k=t;
    t=temp-(a/b)*t;
    return tp_gcd;
}
}

int main()
{
__int64 x,y,m,n,a,b,aa,ll,l;
while(scanf("%I64d %I64d %I64d %I64d %I64d",&x,&y,&m,&n,&l)!=EOF)
{
a=m-n;b=y-x;
if (a<0) {a=-a;b=-b;}
   extended_gcd(a,l);
if (b%d!=0) printf("Impossible\n");
   else
   {
   k=k*b/d;
   t=t*b/d;
   l=l/d;
   if (k>=0)
      k=k%l;
   else k=k%l+l;
   printf("%I64d\n",k);
   }

}
return 0;
}
页: [1]
查看完整版本: 前几天参加比赛的一道小编程题