1.关于扩展欧几里得算法的理解
首先要先学会欧几里得算法(辗转相除法):
int gcd(int a, int b) {
return b ? gcd(b, a % b) : a;
}
/*
c = a % b;
a = b; b = c;
if b != 0 重复上述步骤
if b == 0 return a;
*/
然后去看扩展欧几里得算法:
几个关键点:
因为 ax + by = gcd(a, b)
bx1 + (a % b)y1 = gcd(b, a % b)
gcd(a, b) = gcd(b, a%b)
所以 ax + by = bx1 + (a % b)y1
因为 a % b = a - b[a / b] (这里a / b是计算机的整除)
整理可得解为:
x = y1;
y = x1 - (a / b) * y1;
因为一开始只知道x和y,x1和y1是需要求的,那么我们根据上面发现的特性,发现需要从后往前不断求,
最后得出x和y,所以需要递归的思想。
递归出口是当b = 0时,x = 1,y = 0。(注意这里准确说是xn = 1,yn = 0)
此时x = 1,y = 0(即xn = 1,yn = 0)这个解就是最后的一组解,然后从后往前,不断的得出解,最后就得到了x和y的解
下面通过代码具体解释一下:
直观的代码:
void exgcd(int a, int b, int &d, int &x ,int &y)
//一开始从前往后不断递归的时候,其实只计算了a和b,int &x和int &y是从后往前返回的时候进行的相应赋值和计算
{
if ( !b )
{
d = a;
x = 1;
y = 0; //这里是最后一组解(即xn = 1,yn = 0)
return;
}
int x1,y1;
exgcd( b , a % b , d , x1 , y1 );
/*
这里x1和y1一开始从前往后递归时没有值,当从后往前返回时,得到了值(相当于得到了未来的值)然后通过下面的
x = y1;
y = x1 - ( a / b ) * y1;
这两行代码就得到了这层递归循环的x和y的值(即当前的值,但是当return返回后,又相当于上一层递归循环的未来的值)
*/
x = y1;
y = x1 - ( a / b ) * y1;
return ;
}
简化的代码(不直观):
int exgcd(int a, int b, int &x, int &y)
{
if (!b)
{
x = 1; y = 0;
return a;
}
int d = exgcd(b, a % b, y, x);
/*
这里是我认为比较难想的地方,不直观,下面讲讲我的理解:
这行代码里int d = exgcd(b, a % b, y, x);的y,x一开始从前往后递归时没有值,当从后往前返回时,
y的地方相当于x1的位置,x的地方相当于y1的位置,那么从后往前返回时,返回的值会给y和x赋值,
y得到了返回的x1的值,x得到了返回的y1的值,因为:
x = y1;
y = x1 - ( a / b ) * y1;
所以x不用变,y现在等于x1,x现在等于y1
所以y = y - ( a / b ) * x 就得到了当前这层递归循环y应该的值
这样一来,这层递归循环的x和y的值就都得到了
然后int exgcd(int a, int b, int &x, int &y)这里是x和y的实参,所以刚才得出的x和y的值,
就会传给这里的int &x, int &y,然后继续返回。
*/
y -= (a/b) * x;
return d;
}
2.下面介绍乘法逆元的理解:
1.乘法逆元的作用
当a和b过大时,要进行取模操作,可以如下操作,
(a + b)% p = ((a % p) + (b % p)) % p;
(a - b)% p = ((a % p) - (b % p)) % p;
(a * b)% p = ((a % p) * (b % p)) % p;
但是(a / b)% p不遵循这个法则,所以 可以换一个思想,
(a / b)% p 等同于 (a * b的逆元) % p, 那么问题转化为求b的逆元
设b的逆元为x,则 (b * x) mod p = 1
2.乘法逆元的条件
因为(b * x) mod p = 1
所以 b * x - n * p = 1
即 b * x + n * p = 1
通过前面的学习,我们知道b * x + n * p = k有整数解的条件是
k是gcd(b, p)的整数倍,所以k % gcd(b, p) = 0
当 k = 1 时,1 % gcd(b, p) = 0
所以gcd(b, p) = 1 ,即b 和 p互质是有乘法逆元的充要条件
3.如何求乘法逆元
我们可以用扩展欧几里得算法得到ax + by = c的一组解,但是这个解不一定是我们需要的解。
我们要知道通解是① x = x0 + b * t / gcd ② y = y0 - a * t / gcd
所以我们在做题时要具体问题具体分析,到底需要什么解。
比如在求乘法逆元时,c = 1, 我们要求最小的乘法逆元,即求最小的正整数x,
因为x = x0 + b * t / gcd,所以x % (b / gcd)即可得最小解
4.求乘法逆元的代码
int inv(int b, int p, int c) {
// 求乘法逆元时,c = 1
// 因为ax mod p = 1 即 ax + np = 1
// 则最小逆元即只需求出x最小值即可
int x, y;
int gcd = exgcd(b, p, x, y);
if(c % gcd != 0) return -1; //没有乘法逆元
x *= c / gcd; //求出在ax + np = c的情况下,x的值,因为c = 1,所以没变。
//注意这里求出的x只是一个解而已,不一定是最小解
p /= gcd; // 因为通解是 x = x0 + p * t / gcd;因为求最小逆元时,gcd = 1,所以没变
p = abs(p); //防止P是负数,先取绝对值
int ans = x % p; // 由通解是 x = x0 + p * t / gcd可知,最小的x = x % (p / gcd),因为gcd = 1,也相当于x % p
if(ans <= 0) ans += p;
return ans;
/*
防止x是负数,
x是负数时,x % p + p 得到正数解
*/
}