欧几里得算法的应用

场景一:经典问题----倒水(详细解析)

倒水问题解析

  有两个容器,容积分别为A升和B升,有无限多的水,现在需要C升水。 我们还有一个足够大的水缸,足够容纳C升水。起初它是空的,我们只能往水缸里倒入水,而不能倒出。 可以进行的操作是: 把一个容器灌满; 把一个容器清空(容器里剩余的水全部倒掉,或者倒入水缸); 用一个容器的水倒入另外一个容器,直到倒出水的容器空或者倒入水的容器满。     问是否能够通过有限次操作,使得水缸最后恰好有C升水。

输入:三个整数A, B, C,其中 0 < A , B, C <= 1000000000

输出:0或1,表示能否达到要求。

函数头部:

c语言:1表示可以,0表示不可以 int can(int a,int b,int c);

c++语言: true表示可以,false表示不可以 bool can(int a,int b,int c);

java语言:true表示可以,false表示不可以 public class Main {public static boolean can(int a,int b,int c); } 

      分析

  经典的倒水问题,有不少公司也出了类似的面试题目,有的以选择题形式出现,也有编程题形式出现的,下面做简要的分析:

  对题目做简要的处理分析后,C升水是可以分多次倒入的,假设A > B,那么每次可以倒的水的量为A , B , (A + B) ,( A - B)升水,设置4个因子,分别为x1 ,  x2,  x3,  x4 , (x1 , x2, x3, x4 属于整数),如果可以使得水缸最后恰好有C升水,那么必然存在整数x1 , x2, x3, x4,使得

          Ax1 + Bx2, +  (A + B)x3  + (A - B)x4  = C    等式成立;

  对等式做一定的变换,得到公式

          (x1 + x3 + x4)A + (x2 + x3 - x4)B = C ;     --( 1-1 )

  设 x = x1 + x3 + x4 , y = x2 + x3 - x4 ,  x, y 均为整数;最终得到公式

          xA + yB = C ;                                         --( 1-2 )       

   x1 ,  x2,  x3,  x4可以假设为正整数,用几个for循环可以实现,但是时间复杂度太大,为O(N4),题目中给的范围是0 < A , B, C <= 1000000000;整数在十亿范围,显然运行时间肯定会超过3s ,不符合要求,那有没有更加合适的方法呢,在算法的书里面,有一个算法,与公式( 1-2 ) 不谋而合,是扩展的欧几里德算法,算法描述:

      定理:

  对于不完全为 0 的非负整数 a,b,gcd(a,b)表示 a,b 的最大公约数,必然存在整数对 x,y ,使得 gcd(a,b)=ax+by.    根据欧几里德扩展算法,Gcd(A, B) = Ax + By,求出A和B的最大公约数,如果C能被最大公约数整除Gcd(A, B) 整除,那就可以实现水缸里恰好为C升水;  那题目就直接转换为求A 、B的最大公约数了,求公约数可以用辗转相除法,代码如下:

int mod(int a, int b)    
{   
    return b ? mod(b, a%b) : a;     
}
    
bool can (int a, int b, int c)
{    
    int res;
    res=mod(a, b);
    
    if (0 == c % res)
        return true;
    else
        return false;
} 

同样,附带几个测试用例:

输入:A = 1234567,  B = 7654321 ,  C = 9999999,  输出:result = 1;

输入:A = 9999,  B = 5555,  C = 2222,   输出:result = 1;

输入:A = 1000000000,   B = 2,   C = 1 ,  输出:result = 0.

下面是做一个实例演示:假设A = 11 , B =39 , C = 2,返回值为1,说明可以实现,为方便叙述,采用A(11) , B(39)表示容器,步骤如下:

1、   将容器 B(39) 倒满水,然后3次倒入 A(11) 容器中,那么 B(39) 剩下 39 - 11 * 3 = 6升水,此时A(11)可用;
2、   把 B(39) 中的 6 升水全部倒入容器 A(11) 中,那么容器 A(11) 中有 6 升水,5 升是空的,此时B(39)可用;
3、   把 B(39) 倒满水,然后往第2步得到的 A(11 )倒入直到 A(11) 满为止,那B(39)剩下39 - 5 = 34 升水,清空 A(11) ,此时A(11)可用;

4、   步骤3得到的 B(39) 容器有34升水,3次倒入 A(11)中,那 B(39)中剩下 34 - 11 * 3= 1升水,此时 A(11)可用;

5、   把步骤4的 1 升水倒入水缸,清空 A(11) 和 B(39),重做步骤1 - 4,再往水缸倒入1升水,那水缸里就是 1 + 1 = 2 升水了。

上面其实是辗转相除法的体现

——————————————————————————————————————————————

场景二:扩展欧几里得算法

一、题目描述

    在平面上有一个两端无限延伸的数组如下图所示,0为起点,1是终点,现在有四种走法,向正方向走a步,向负方向走a步,向正方向走b步,向负方向走b步。在任给两个数a,b问能否从起点走到终点。

二、样例

    输入:a=4,b=11

    输入:Yes(a+a+a-b)

三、解题报告

    该题实际要求的是,满足ax+by=1的整数解x,y。当gcd(a,b)!=1时是无解的,因为,在ax+by=1中a,b,x,y,1,都是整数,假设a,b的最大公倍数为c,即c=gcd(a,b),则方程可以化解为:(a/c)x+(b/c)y=1/c,令a1=a/c,b1=b/c 则有a1x+b1y=1/c,其中a1,b1,x,y都是整数如果1/c不是整数则方程无解,所有方程有整数解的条件是gcd(a,b)=c=1。

四、扩展欧几里得算法

    所谓的扩展欧几里得算法就是用来求解方程:ax+by=gcd(a,b)的算法。由辗转相除法可知gcd(a,b)=gcd(b,a%b).所以有ax+by=gcd(a,b) 和 bx2+(a%b)y2=gcd(a,b).第二个式子变形可得到:ay1+b(x1-(a/b)×y1)=gcd(a,b),故可以的得到:x=y1,y=(x1-(a/b)×y1)。而且当b=0时,gcd(a,b)=a,故可以得到:1*a+0*b=0,即x=1,y=0.

五、欧几里得算法扩展算法代码

int exgcd(int a,int b,int &x,int&y)
{
    if(b==0)
    {
        x=1;
        y=0;
        return a;
    }
    int d=exgcd(b,a%b,y,x);//交换x与y位置
    y=y-(a/b)*x;
    return d;
}

如果上面代码看不懂,该分类里面有欧几里得算法原理的文章,有解释。

六、本题代码

#include <iostream>
#include <cstdio>
using namespace std;
 
int exgcd(int a,int b,int &x,int&y)
{
    if(b==0)
    {
        x=1;
        y=0;
        return a;
    }
    int d=exgcd(b,a%b,y,x);//交换x与y位置
    y=y-(a/b)*x;
    return d;
}
 
int main()
{
    int a,b,c;
    int x,y;
    scanf("%d%d",&a,&b);//输入的一个点
    if(a<=b)//大的放前
        c=exgcd(a,b,x,y);
    else
    {
        c=exgcd(b,a,x,y);
        int z=x;
        x=y;
        y=z;
    }
    if(c==1)
    {
        printf("Yes(%d*%d",x,a);
        if(y>0)
            printf("+");
        printf("%d*%d)\n",y,b);
    }
    else
        printf("No\n");
    return 0;
}

猜你喜欢

转载自blog.csdn.net/sinat_38816924/article/details/82990123