转自这里
欧几里得算法
欧几里德算法又称辗转相除法,用于计算两个整数a,b的最大公约数。
基本算法:设a=qb+r,其中a,b,q,r都是整数,则gcd(a,b)=gcd(b,r),即gcd(a,b)=gcd(b,a%b)。
递归实现:
int gcd(int a,int b)
{
if(b==0)
return a;
return
gcd(b,a%b);
}
迭代实现
int Gcd(int a, int b)
{
while(b != 0)
{
int r = b;
b = a % b;
a = r;
}
return a;
}
二.扩展欧几里德算法
基本算法:对于不完全为 0 的非负整数 a,b,gcd(a,b)表示 a,b 的最大公约数,必然存在整数对 x,y ,使得 gcd(a,b)=ax+by。
我们观察到:欧几里德算法停止的状态是: a= gcd , b = 0 ,那么,这是否能给我们求解 x y 提供一种思路呢?因为,这时候,只要 a = gcd 的系数是 1 ,那么只要 b 的系数是 0 或者其他值(无所谓是多少,反正任何数乘以 0 都等于 0 但是a 的系数一定要是 1),这时,我们就会有: a1 + b0 = gcd
当然这是最终状态,但是我们是否可以从最终状态反推到最初的状态呢?
假设当前我们要处理的是求出 a 和 b的最大公约数,并求出 x 和 y 使得 ax + by= gcd ,而我们已经求出了下一个状态:b 和 a%b 的最大公约数,并且求出了一组x1 和y1 使得: bx1 + (a%b)y1 = gcd , 那么这两个相邻的状态之间是否存在一种关系呢?
我们知道: a%b = a - (a/b)*b(这里的 “/” 指的是整除,例如 5/2=2 , 1/3=0),那么,我们可以进一步得到:
gcd = b*x1 + (a-(a/b)*b)*y1
= b*x1 + a*y1 – (a/b)*b*y1
= a*y1 + b*(x1 – a/b*y1)
对比之前我们的状态:求一组 x 和 y 使得:a*x + b*y = gcd ,是否发现了什么?
这里:
x = y1
y = x1 – a/b*y1
以上就是扩展欧几里德算法的全部过程,依然用递归写:
int exgcd(int a,int b,int &x,int &y)
{
if(b==0)
{
x=1;
y=0;
return a;
}
int ans=exgcd(b,a%b,x,y);
int t=x;
x=y;
y=t-a/b*y;
return ans;
}
这就是理论部分,欧几里德算法部分我们好像只能用来求解最大公约数,但是扩展欧几里德算法就不同了,我们既可以求出最大公约数,还可以顺带求解出使得:
ax + by = gcd 的通解 x 和 y
扩展欧几里德算法的应用主要有以下三方面:
(1)求解不定方程;
(2)求解模线性方程(线性同余方程);
(3)求解模的逆元;
其中扩展欧几里得算法一个重要的应用在求解形如 ax +by = c 的特解,比如一个数对于另一个数的乘法逆元
乘法逆元
什么叫乘法逆元?
ax≡1 (mod m)
这里,我们称 x 是 a 关于 m 的乘法逆元
这怎么求?这里我们利用扩展欧几里得算法,等价为: ax + my = 1
我们发现当gcd(a , m) != 1 的时候是没有解的,这也是 ax + by = c 有解的充要条件: c % gcd(a , b) == 0
一般,我们能够找到无数组解满足条件,但是一般是让你求解出最小的那组解,怎么做?我们求解出来了一个特殊的解 x0 那么,我们用 x0 % m其实就得到了最小的解了。为什么?
可以这样思考:
x 的通解不是 x0 + mt 吗?
那么,也就是说, a 关于 m 的逆元是一个关于 m 同余的,那么根据最小整数原理,一定存在一个最小的正整数,它是 a 关于m 的逆元,而最小的肯定是在(0 , m)之间的,而且只有一个,这就好解释了。
但是,由于问题的特殊性,有时候我们得到的特解 x0 是一个负数,还有的时候我们的 m 也是一个负数这怎么办?
当 m 是负数的时候,我们取 m 的绝对值就行了,当 x0 是负数的时候,x0% m 的结果仍然是负数(在计算机计算的结果上是这样的,虽然定义的时候不是这样的),这时候,我们仍然让 x0 对abs(m) 取模,然后结果再加上abs(m) 就行了,于是,我们不难写出下面的代码求解一个数 a 对于另一个数 m 的乘法逆元:
int cal(int a,int m)
{
int x,y,ans,gcd;
gcd=exgcd(a,m,x,y);
if(1%gcd!=0)///无解
{
return -1;
}
x=x*1/gcd;
m=abs(m);
ans=x%m;
if(ans<=0)
{
ans=ans+m;
}
return ans;
}
这里给出一道例题作为乘法逆元的模板
给出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
#include <iostream>
#include <cstdio>
using namespace std;
void exgcd(int a,int b,int &x,int &y)
{
if(b==0)///递归结束条件
{
x=1;
y=0;
return ;
}
exgcd(b,a%b,x,y);
int t=x;
x=y;
y=t-a/b*y;
}
int main()
{
int n,m;
int x,y;
scanf("%d%d", &m,&n);
exgcd(m,n,x,y);
while(x<0)
{
x=x+n;
}
printf("%d\n",x);
return 0;
}
扩展欧几里得算法
给定模数m,求a的逆元相当于求解ax=1(mod m)
这个方程可以转化为ax-my=1
然后套用求二元一次方程的方法,用扩展欧几里得算法求得一组x0,y0和gcd
检查gcd是否为1
gcd不为1则说明逆元不存在
若为1,则调整x0到0~m-1的范围中即可
( 用我自己的话解释一下,①:extgcd 目的 求 x ,y ; ②:逆元 目的 求 x; ③: so 用extgcd 来求逆元 )
一个数有逆元的充要条件是gcd(a,n)=1,如果gcd(a,n)>1,则不存在逆元:比如:18 12
循环找解法
给定模m和需要求逆的数x,直接暴力枚举1~m-1
检查是否有x*i=1(mod m)
这种算法可以应用与写暴力、对拍、模数较小,求逆次数少的情况
时间复杂度O(m)