首页 > 技术文章 > 继逆元之扩展欧几里得算法

kongbursi-2292702937 2019-03-23 23:23 原文

一、首先我们要先知道扩展欧几里得原理(ax+by=gcd(a,b))其实并没有要求a与b必须互质,a与b互质只是他的一种情况,在下面我将讲解一个a与b互质与不互质的题目。。。。。。不多说先看原理!
即如果a、b是整数,那么一定存在整数x、y使得ax+by=gcd(a,b)。
换句话说,如果ax+by=m有解,那么m一定是gcd(a,b)的若干倍。(可以来判断一个这样的式子有没有解)
有一个直接的应用就是 如果ax+by=1有解,那么gcd(a,b)=1;
要求出这个最大公因数gcd(a,b),我们最容易想到的就是古老悠久而又相当强大的辗转相除法:
int gcd(int a,int b){    return b==0?a:gcd(b,a%b);}
但是,对于上面的式子ax+by=m来说,我们并不仅仅想要知道有没有解,而是想要知道在有解的情况下这个解到底是多少。
所以,扩展欧几里得
        当到达递归边界的时候,b==0,a=gcd(a,b) 这时可以观察出来这个式子的一个解:a*1+b*0=gcd(a,b),x=1,y=0,注意这时的a和b已经不是最开始的那个a和b了,所以我们如果想要求出解x和y,就要回到最开始的模样。
        初步想法:由于是递归的算法,如果我们知道了这一层和上一层的关系,一层一层推下去,就可以推到最开始的。类似数学上的数学归纳法。
        假设当前我们在求的时a和b的最大公约数,而我们已经求出了下一个状态:b和a%b的最大公因数,并且求出了一组x1和y1使得                          b*x1+(a%b)*y1=gcd
(注意在递归算法中,永远都是先得到下面一个状态的值)
这时我们可以试着去寻找这两个相邻状态的关系:
首先我们知道:a%b=a-(a/b)*b;带入:
b*x1 + (a-(a/b)*b)*y1
= b*x1 + a*y1 – (a/b)*b*y1
= a*y1 + b*(x1 – a/b*y1) = gcd   发现 x = y1 , y = x1 – a/b*y1

原文:https://blog.csdn.net/destiny1507/article/details/81750874
 
代码:
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include<string.h>
#include <math.h>
#include<algorithm>
using namespace std;
typedef long long ll;
void extgcd(ll a,ll b,ll& d,ll& x,ll& y)
{
    if(!b)
    {
        d=a;
        x=1;
        y=0;
    }
    else
    {
        extgcd(b,a%b,d,y,x);
        y-=x*(a/b);
    }
}
int ModularInverse(int a,int b)
{
    
    ll d,x,y;
    extgcd(a,b,d,x,y);
    return d==1?(x+b)%b:-1;  //返回的结果就是(1/a)mod(b)的结果
    // complete this part
}
int main()
{
    printf("%d\n",ModularInverse(2,3));//结果是2
    /*
    2*x+3*y=1mod(3)  //那个x就是(1/2)mod(3)的结果,y的话不用管,因为3*y取模于3都是0
    */
    return 0;
}

 

二、题目(不互质)
1、
两只青蛙在网上相识了,它们聊得很开心,于是觉得很有必要见一面。它们很高兴地发现它们住在同一条纬度线上,于是它们约定各自朝西跳,直到碰面为止。可是它们出发之前忘记了一件很重要的事情,既没有问清楚对方的特征,也没有约定见面的具体位置。不过青蛙们都是很乐观的,它们觉得只要一直朝着某个方向跳下去,总能碰到对方的。但是除非这两只青蛙在同一时间跳到同一点上,不然是永远都不可能碰面的。为了帮助这两只乐观的青蛙,你被要求写一个程序来判断这两只青蛙是否能够碰面,会在什么时候碰面。
我们把这两只青蛙分别叫做青蛙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"
Sample Input
1 2 3 4 5
Sample Output
4
这一道题目的关系其实很好推出来 (x+mt)%L=(y+nt)%L 或者是 (x+mt)-(y+nt)=y-x;
(n-m)t+kL=x-y
此时这可以看作一个ax+by=c的方程 其中a=n-m,b=k,c=x-y
按照扩展欧几里得原理ax+by=gcd(a,b),但是现在的c并不是,但是我们可以将ax+by=gcd(a,b)《一式》化成ax+by=c《二式》的形式
可以将一式左右都乘与c/gcd(a,b),这样我们可以得到啊a`x+b`y=c
这样就可以带入扩展欧几里得公式中从而得到一组解
但是此时得到的x0和y0并不是原式的,这个解是我们将原式乘与c/gcd(a,b)而得到的,所以我们想要得到原式的解,我们可以利用a`x0=ax且a`=a*(c/gcd(a,b))
易知x=c/gcd(a,b)*x0
y0=c/gcd(a,b)*y0
但是这并不是题目上要的最后结果,题目上要的是最小值,我们只是得到了一组解,这是一个二元函数,是一条直线,所以有多个解,既然是一条直线,x和y就满足一个参数方程

      x = x0 + (b/gcd)*k 

      y = y0 - (a/gcd)*k     将这两个带入方程就是看到x0和y0后面的刚好消掉

在本题我们要求x0,我们已知的是x,所以对(b/gcd)取模,判断是否小于0,如果是就+=(b/gcd)(可以上代码了“——”)

 1 #include<stdio.h>
 2 #include<string.h>
 3 typedef long long ll;
 4 long long gcd(long long a,long long b,long long &x,long long &y)
 5 {
 6 //这种方法求出来的不是最小值,而是一个通解
 7 //x=x0+gcd(a,b)*k;
 8 //y=y0+gcd(a,b)*k;
 9     if(b==0)
10     {
11         x=1;
12         y=0;
13         return a;
14     }
15     long long r=gcd(b,a%b,x,y); long long t=x;
16     x=y;
17     y=t-(a/b)*y;
18     return r;
19 }
20 //ll gcd(ll a,ll b,ll &x,ll &y)
21 //
22 //{
23 //
24 //    ll r,t;
25 //
26 //    if(!b)
27 //
28 //    {
29 //
30 //        x=1;
31 //
32 //        y=0;
33 //
34 //        return a;
35 //
36 //    }
37 //
38 //    r=gcd(b,a%b,x,y);
39 //
40 //    t=x;
41 //
42 //    x=y;
43 //
44 //    y=t-a/b*y;
45 //
46 //    return r;
47 //
48 //}
49 int main()
50 {
51     long long a,f,g,x,y,m,n,l;
52     scanf("%lld%lld%lld%lld%lld",&x,&y,&m,&n,&l);
53     g=x-y;
54     a=gcd(n-m,l,x,y);
55     if(g%a==0)
56     {
57         x=(g/a)*x;  
58         f=l/a;
59         //y=c/a*d;
60 //        if(x>=0)
61 //            x=x%
62         // x=(x%a+a)%a;
63         if(x<0)
64             x=(x%f+f)%f;
65         else x%=f;
66         printf("%lld\n",x);
67     }
68     else printf("Impossible\n");
69     return 0;
70 }

 

 

2、(互质)

给出2个数M和N(M < N),且M与N互质,找出一个数K满足0 < K < N且K * M % N = 1,如果有多个满足条件的,输出最小的。
 

Input输入2个数M, N中间用空格分隔(1 <= M < N <= 10^9)Output输出一个数K,满足0 < K < N且K * M % N = 1,如果有多个满足条件的,输出最小的。Sample Input

2 3

Sample Output

2
很容易得到关系式k*m*x+n*y=1; a=k*m,b=n
gcd(a,b)=1, 但他们互质的时候题目变得不那么复杂,不用将ax+by=c 化成ax+by=gcd(a,b)

这样就可以直接把他们带入到欧几里得公式中

为了防止最后x为负值,我们就要不断加上n

为什么加n呢。。

x = x0 + (b/gcd)*k 

y = y0 - (a/gcd)*k

此时gcd=1;x就是不断加上b,b就是n

贴代码:

 1 #include<stdio.h>
 2 #include<string.h>
 3 #include<algorithm>
 4 using namespace std;
 5 typedef long long ll;
 6 ll m,n,k;
 7 void gcd(ll a,ll b,ll &x,ll &y) 
 8 { 
 9     if(b==0) 
10     { 
11         x=1; 
12         y=0; 
13         return; 
14     } 
15     gcd(b,a%b,x,y); 
16     int t=x; 
17     x=y; 
18     y=t-(a/b)*y; 
19 } 
20 int main()
21 {
22  int i,j,k;
23  ll x,y;
24  scanf("%lld%lld",&m,&n);
25  gcd(m,n,x,y);
26  while(x<0)
27  {
28   x+=n;
29  }
30  printf("%d\n",x);
31  return 0;
32 }

 

 

推荐阅读