zoukankan      html  css  js  c++  java
  • 拓展欧几里得算法

    欧几里得算法

    欧几里德算法又称辗转相除法,用于计算两个整数a,b的最大公约数。基本算法:设a=qb+r,其中a,b,q,r都是整数,则gcd(a,b)=gcd(b,r),即gcd(a,b)=gcd(b,a%b)。 证明略去了。

    基本代码实现:

    1 int gcd(int a,int b)
    2 {
    3     if(b==0)
    4         return a;
    5     return 
    6         gcd(b,a%b);
    7 }

    扩展欧几里得算法

    扩展欧几里德算法是欧几里得算法的扩展。

    已知整数a、b,扩展欧几里得算法可以在求得a、b的最大公约数的同时,能找到整数x、y(其中一个很可能是负数),使它们满足贝祖等式ax + by = gcd(a, b).。有两个数a,b,对它们进行辗转相除法,可得它们的最大公约数——这是众所周知的。然后,收集辗转相除法中产生的式子,倒回去,可以得到ax+by=gcd(a,b)的整数解。

    用类似辗转相除法,求二元一次不定方程47x+30y=1的整数解。

    • 47=30*1+17
    • 30=17*1+13
    • 17=13*1+4
    • 13=4*3+1

    然后把它们改写成“余数等于”的形式

    • 17=47*1+30*(-1) //式1
    • 13=30*1+17*(-1) //式2
    • 4=17*1+13*(-1) //式3
    • 1=13*1+4*(-3)

    然后把它们“倒回去”

    • 1=13*1+4*(-3) //应用式3
    • 1=13*1+[17*1+13*(-1)]*(-3)
    • 1=13*4+17*(-3) //应用式2
    • 1=[30*1+17*(-1)]*4+17*(-3)
    • 1=30*4+17*(-7) //应用式1
    • 1=30*4+[47*1+30*(-1)]*(-7)
    • 1=30*11+47*(-7)

    得解x=-7, y=11。

    基本算法:对于不完全为 0 的非负整数 a,b,gcd(a,b)表示 a,b 的最大公约数,必然存在整数对 x,y ,使得 gcd(a,b)=ax+by。

     1 证明:设 a>b。
     2 
     3   推理1,显然当 b=0,gcd(a,b)=a。此时 x=1,y=0//推理1
     4 
     5   推理2,ab!=0 6 
     7   设 ax1+by1=gcd(a,b);
     8 
     9   bx2+(a mod b)y2=gcd(b,a mod b);
    10 
    11   根据朴素的欧几里德原理有 gcd(a,b)=gcd(b,a mod b);
    12 
    13   则:ax1+by1=bx2+(a mod b)y2;
    14 
    15   即:ax1+by1=bx2+(a-(a/b)*b)y2=ay2+bx2-(a/b)*by2;
    16 
    17   根据恒等定理得:x1=y2; y1=x2-(a/b)*y2;//推理2
    18 
    19      这样我们就得到了求解 x1,y1 的方法:x1,y1 的值基于 x2,y2.
    20 
    21    上面的思想是以递归定义的,因为 gcd 不断的递归求解一定会有个时候 b=0,所以递归可以结束。

    扩展欧几里德的递归代码:

     1 #include <iostream>
     2 using namespace std;
     3 
     4 int exgcd(int a,int b,int & x,int & y){
     5     if(b == 0){
     6         //根据上面的推理1,基本情况
     7         x = 1;
     8         y = 0;
     9         return a;
    10     }
    11     int r = exgcd(b, a%b, x, y);
    12     //根据推理2
    13     int t = y;
    14     y = x - (a/b)*y;
    15     x = t;
    16     return r;
    17 }
    18 
    19 int main() {
    20     int x,y;
    21     exgcd(47,30,x,y);
    22     cout << "47x+30y=1 的一个整数解为: x=" << x << ", y=" << y << endl;
    23     return 0;
    24 }

    扩展欧几里德算法的应用

    (1)求解不定方程

    用扩展欧几里得算法解不定方程ax+by=c;

    这个应该比较好理解了,两个可以同乘以k

    1 bool linear_equation(int a,int b,int c,int &x,int &y)
    2 {
    3     int d=exgcd(a,b,x,y);
    4     if(c%d)
    5         return false;
    6     int k=c/d;
    7     x*=k; y*=k;    //求得的只是其中一组解
    8     return true;
    9 }

    (2)求解模线性方程(线性同余方程

    同余方程 ax≡b (mod n) (也就是 ax % n = b) 对于未知数 x 有解,当且仅当 gcd(a,n) | b (也就是 b % (gcd(a,n))==0 )。且方程有解时,方程有 gcd(a,n) 个解。

    求解方程 ax≡b (mod n) 相当于求解方程 ax+ ny= b, (x, y为整数)

    1 在方程  3x ≡ 2 (mod 6) 中, d = gcd(3,6) = 33 不整除 2,因此方程无解。
    2 
    3 在方程 5x ≡ 2 (mod 6) 中, d = gcd(5,6) = 11 整除 2,因此方程在{0,1,2,3,4,5} 中恰有一个解: x=4

    证明略去,直接说算法:

    首先看一个简单的例子:

    5x=4(mod3)

    解得x = 2,5,8,11,14…….

    由此可以发现一个规律,就是解的间隔是3.

    那么这个解的间隔是怎么决定的呢?

    如果可以设法找到第一个解,并且求出解之间的间隔,那么就可以求出模的线性方程的解集了.

    我们设解之间的间隔为dx.

    那么有

    a*x = b(mod n);

    a*(x+dx) = b(mod n);

    两式相减,得到:

    a*dx(mod n)= 0;

    也就是说a*dx就是a的倍数,同时也是n的倍数,即a*dx是a 和 n的公倍数.为了求出dx,我们应该求出a 和 n的最小公倍数,此时对应的dx是最小的.

    设a 和 n的最大公约数为d,那么a 和 n 的最小公倍数为(a*n)/d.

    即a*dx = a*n/d;

    所以dx = n/d. (d = gcd(a,n) )

    因此解之间的间隔就求出来了.

     1 bool modular_linear_equation(int a,int b,int n)
     2 {
     3     int x,y,x0,i;
     4     int d=exgcd(a,n,x,y);
     5     if(b%d)
     6         return false;
     7     x0=x*(b/d)%n;   //特解
     8     for(i=1;i<d;i++)
     9         printf("%d
    ",(x0+i*(n/d))%n);
    10     return true;
    11 }

    (3)求解模的逆元;

    同余方程ax≡b (mod n),如果 gcd(a,n)== 1,则方程只有唯一解。

    在这种情况下,如果 b== 1,同余方程就是 ax=1 (mod n ),gcd(a,n)= 1。

    这时称求出的 x 为 a 的对模 n 乘法的逆元。

    对于同余方程 ax= 1(mod n ), gcd(a,n)= 1 的求解就是求解方程

    ax+ ny= 1,x, y 为整数。这个可用扩展欧几里德算法求出,原同余方程的唯一解就是用扩展欧几里德算法得出的 x 。

    练习题

    青蛙的约会

          思路:两只青蛙跳一次所花费的时间相同,我们设其为t,则x+mt是青蛙A从坐标原点到终点所走的距离,y+nt是B走的距离,要想碰面,则他们相减一定是地面周长的整数倍,设为k*L;则:(x+mt)-(y+nt)=kl;变形得:(m-n)t-(y-x)=kL;即有(m-n)t mod L=y-x;为线性同余方程。此方程有解当且仅当y-x能被m-n和L的最大公约数(记为gcd(m-n,L)),即gcd(m-n,L)|y-x。这时,如果x0是方程的一个解,即当t=x0时,(m-n)t mod L=y-x成立,那么所有的解可以表示为:
    {x0+k(L/gcd(m-n,L))|(k∈整数)}。

    欧几里得算法的拓展应用中有如下三条定理:

       定理一:如果d = gcd(a, b),则必能找到正的或负的整数k和l,使d = a*x+ b*y。

       定理二:若gcd(a, b) = 1,则方程ax ≡ c (mod b)在[0, b-1]上有唯一解。

       定理三:若gcd(a, b) = d,则方程ax ≡ c (mod b)在[0, b/d - 1]上有唯一解。

          证明:上述同余方程等价于ax + by = c,如果有解,两边同除以d,就有a/d * x + b/d * y = c/d,即a/d * x ≡ c/d (mod b/d),显然gcd(a/d, b/d) = 1,所以由定理二知道x在[0, b/d - 1]上有唯一解。所以ax + by = c的x在[0, b/d - 1]上有唯一解,即ax ≡ c (mod b)在[0, b/d - 1]上有唯一解。

          如果得到ax ≡ c (mod b)的某一特解X,那么令r = b/gcd(a, b),可知x在[0, r-1]上有唯一解,所以用x = (X % r + r) % r就可以求出最小非负整数解x了!(X % r可能是负值,此时保持在[-(r-1), 0]内,正值则保持在[0, r-1]内。加上r就保持在[1, 2r - 1]内,所以再模一下r就在[0, r-1]内了)。

    代码如下:

     1 #include<stdio.h>
     2 __int64 exgcd(__int64 a,__int64 b,__int64 &x,__int64 &y)//欧几里得算法的扩展
     3 {
     4     __int64 r,t;
     5     if(b==0)
     6     {
     7         x=1;
     8         y=0;
     9         return a;
    10     }
    11     r=exgcd(b,a%b,x,y);
    12     t=x;
    13     x=y; 
    14     y=t-a/b*y;
    15     return r;
    16 }
    17 int main()
    18 {
    19     __int64 x,y,m,n,l,xx,yy,d,r;
    20     scanf("%I64d%I64d%I64d%I64d%I64d",&x,&y,&m,&n,&l);
    21     d=exgcd(n-m,l,xx,yy);
    22     if((x-y)%d!=0) printf("Impossible
    ");
    23     else {
    24             xx=xx*((x-y)/d);
    25             r=l/d;
    26             xx=(xx%r+r)%r;//求出最小非负整数解
    27             printf("%I64d
    ",xx);
    28         }
    29     return 0;
    30 }
    View Code
  • 相关阅读:
    Redis概述与安装
    CentOS-Linux系统下安装MySQL
    CentOS-Linux系统下安装Tomcat
    CentOS-Linux系统下安装JDK
    占位符
    设计模式之装饰者模式
    Servlet3.0
    注解入门
    PHP如何实现99乘法表?
    2017年PHP程序员未来路在何方?(转载)
  • 原文地址:https://www.cnblogs.com/--lr/p/7372394.html
Copyright © 2011-2022 走看看