北大POJ题目---青蛙的约会

   今天在北大POJ上做到一道题——青蛙的约会,连续研究了数个小时,提交了共有50多次,每次都是以wrong anwser或是Time Limit Exceeded被打了回来。后来没吃晚饭,当看到最后一次显示“Accepted”时,内心的焦躁瞬间舒展开来了。

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

Input

输入只包括一行5个整数xymnL,其中x≠y < 20000000000 < mn < 20000000000 < L < 2100000000

Output

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

 

  

   这里可以有两种方法,第一种稍微好理解一些,不过让人感觉诧异的是同样的算法,C++实现的结果是 Time Limit Exceeded,而换做C语言却通过了对POJ的提交。这也就不得不说明C语言虽然功能更为单调,但是由于更接近底层操作,它的运行速度仍是一大优势。

 

      方法一分析:  两只青蛙的初位移差:x-y,跳跃能力差值m-n。简单的相对性的原理,我们便可以假设是一只青蛙在跳,跳跃能力是m-n;初始位置x-y;即理解为一只青蛙从x-y位置开始,以m-n的跳跃能力,能否在若干次跳跃后回到0点上。分析一下下就会想到,如果不能,这只青蛙将会以某种规律在除原点外的一些点上循环跳下去。那么我们就以青蛙每过一次跳经原点后距离远点大小为标记,如果有一次恰好跳在原点上,那么就得出了答案,如果若干次后,它不但没有跳到原点上,反而又跳到了出发位置上,那么说明它将以此规律无限循环跳下去而永远也跳不到原点上,这时就可以大胆输出“Impossible”了。下面是用C++将其实现的代码:

#include 

 

using namespace std;

 

int main()

{

    int x,y,m,n,l;

    cin>>x>>y>>m>>n>>l;

    if(m==n)

    {

        cout<<"Impossible"<

        return 0;

    }

    else

    {

    int c,z;

    if(m>n)

    {

        c=m-n;

        z=(y-x+l)%l;

    }

    else

    {

        c=n-m;

        z=(x-y+l)%l;

    }

    int w=z%c;

    int r=z/c;

    while(true)

    {

        if(z%c==0)

        {

            cout<

            break;

        }

        else

        {

            int p=z%c+l;

            r+=p/c;

            z=p%c;

        }

        if(z==w)

        {

            cout<<"Impossible"<

            break;

        }

    }

    }

    return 0;

}

可惜的是这个代码提交后显示程序运算超时了(C语言翻译后居然可以通过,很令人更喜欢C++的自己吐槽。。。),虽然算法好理解,但是似乎是把更多的运算与判断都交给了计算机,貌似还得另寻途径。

 

下面方法2是理解上比上面更复杂点的解决办法,毕竟要给计算机省时省力,还得多用人脑细胞吧。

    方法二分析:要另两只青蛙相遇,必须是累计跳跃布数是L的整数i倍,另跳k步,即(k*m+x-k+n+y=L*i i=0,1,2,3......)。简单整理后:L*i+(n-m)*k=x-y;其模型就是a*i+b*k=tabt都是已知整数)关于ik是否有整数解。那好,先把这个问题放一下,现在引入一个很关键的数学性质:就是如果ab如果是互质的,那么他们的线性组合可以得到任意的整数,证明如下:我们先证明a*i+b*k可以等于1,那么就能说明可以等于任意整数(直接乘以相应倍数就可以)。设nab线性组合可以得到的最小的正整数。令m是另外一个可以线性组合得到的数:m=f*n+r0<=r);那么f*n也是属于ab线性组合可以得到的,那么r就也是了;所以r就只能是0了。这样的话我们就得到ab线性组合可以得到的数的集合{p}中所有的整数都是最小的n的倍数。我们另互质的ab线性组合得到的最小的数仍是n,那么                   a*x0+b*y0=n

                        又有:a*x0+1+b*y0=n+a

                              a*x0+b*y0+1=n+b

所以n+an+b都是n的倍数,条件中ab互质,那么n的值就只能是1了;

证到这里,我们把得到的结论总结一下:

如果ab互质,那么这道题肯定有解的(确定不会输出“Possible”了),如果ab不互质,那么如果t中含ab最大公约数w,这时都除以w,仍然可以说明不用输出“Possible”了。

所以程序首要任务就是算出ab的最大公约数。对于此,我们可以用欧几里得的辗转相除法则,即ab的最大公约数等于ba%b%是取余符号)的最大公约数。

关于辗转相除的正确性,为了一窥其本质,试图证明如下:

   设ab的最大公约数为n

  另不妨令a=k*b+cb=g*n;那么c一定也是n的倍数了,即n也是bc的最大公约数。所以辗转相除的正确性就证明了。

 求最大公约数,根据上述原理,其C++的实现如下:

 int  getMostCommenDivisor(__int64 a,__int64 b)

{

    if(b==0)

    {

        return a;

    }

    return getMostCommenDivisor(b,a%b);

}

 

然后判断得到的最大公约数是否能被等式左边的t整除。然后怎样算出准确的跳跃步数就是下一个任务了;

关于a*i+b*k=t的解,等效于计算(a/n)*i+(b/n)*k=t/n 的解。有一个办法,就是暴力算法(穷举),从0开始,两个for循环算下去,但是这样转了一大圈还不如第一种方法来得省计算机资源。所以得找其他办法;

其实我们可以容易证明:a*i+b*k   =   b*i0+a%b*k0  =   a*k0+b*i0-a/b*k0); 即i=k0k=i0-a/b*k0

递归的方法可很好的实现这个算法:

 void getAnwser(__int64 a,__int64 b,__int64 &x,__int64 &y)

{

 if( b==0 )

 {

  x=1;

  y=0;

  return;

 }

 getAnwser(b,a%b,x,y);

  __int64 w=x;

        x=y;

      y=w-a/b*y;

      return;

}

而(a/n)*i+(b/n)*k=t/n 的解一定是有无数组的,方程解如下: 其中t为任意整数

 i = t/n* i0 + b/n * t 

k = t/n * k0 - a /n* t  

这也可以理解为,如果两只青蛙能够相遇,继续跳下去的话,他们将能够相遇无数次。

而我们要的答案是首次相遇时所跳的步数,即我们要求的大于零且最小的那个i的解,这在程序中也是必须要体现的。说到这里,所有的要解决的算法都已分析完,完整的C++代码如下:

 

#include 

using namespace std;

 

 

void getAnwser(__int64 a,__int64 b,__int64 &x,__int64 &y)   //计算初解的函数

{

 if( b==0 )

 {

  x=1;

  y=0;

  return;

 }

 getAnwser(b,a%b,x,y);

  __int64 w=x;

        x=y;

      y=w-a/b*y;

      return;

}

 

int getMostCommenDivisor(__int64 a,__int64 b)  //得到最大公约数的函数

{

    if(b==0)

    {

        return a;

    }

    return getMostCommenDivisor(b,a%b);

}

 

 

int main()

{

    __int64 x,y,m,n,l,a1,a2,a3,r;

   while(cin>>x>>y>>m>>n>>l)

   {

    a1=n-m;

    a2=l;

    a3=x-y;

    r=getMostCommenDivisor(a1,a2);

    if(a3%r!=0)                   //判断是否能被整除,从而知道能否相遇

       cout<<"Impossible"<

    else

     {

        __int64 p1,p2,t;

        a1=a1/r;

        a2=a2/r;

        a3=a3/r;

       getAnwser(a1,a2,p1,p2);       

       p1=a3*p1;

       while(p1>0)           //我们要得到的是最小的那个正整数p1

        p1-=a2;

        while(p1<0)         //保证是刚刚大于零的那个最小p1

         p1+=a2;   

       cout<

     }

   }

    return 0;

}

 

 

由于题目中给出0 < L < 2100000000,接近int型的上限,在计算步数上时可能出现溢出,所以要使用占用内存8个字节的__int64型,从而保证得数是有效的。


你可能感兴趣的:(数论)