![46e95f1b34723481d847e1b97eb7ecbc.png](https://i-blog.csdnimg.cn/blog_migrate/cc419b288dca3d0c1efc9ac460199a7a.jpeg)
乘法逆元
在中国剩余定理的计算里,需要求一个数字在一个模下的逆元,也就是对于给定的 a,b,找到方程
![equation?tex=a+a%5E%2A+%5Cequiv+1+%5Cpmod%7Bb%7D](https://i-blog.csdnimg.cn/blog_migrate/57536f7270037aac8c666bd987773856.png)
![equation?tex=a%5E%2A](https://i-blog.csdnimg.cn/blog_migrate/bc5f9d4b7bd15137502325a8c547c98f.png)
根据同余的定义,有
![equation?tex=b+%5Cmid+%28a+a%5E%2A+-+1%29](https://i-blog.csdnimg.cn/blog_migrate/0e773e4a442f4c1a518b5a348d3977e7.png)
![equation?tex=k](https://i-blog.csdnimg.cn/blog_migrate/9d4a1163d2eb1bb72228d1279049c2d8.png)
![equation?tex=bk%3Da+a%5E%2A-1](https://i-blog.csdnimg.cn/blog_migrate/25ea2e573a1e5d98930d2386fc0453d9.png)
![equation?tex=a+a%5E%2A-bk%3D1](https://i-blog.csdnimg.cn/blog_migrate/fa995a3719b1551c8a0112ec7c4b4081.png)
这个形式恰好符合裴蜀定理
![equation?tex=ax%2Bby%3D1](https://i-blog.csdnimg.cn/blog_migrate/8575f617492fd9cc03055f66a388e178.png)
![equation?tex=%28a%2Cb%29%3D1](https://i-blog.csdnimg.cn/blog_migrate/3e7365f287fc8ce59e88b3adc0a2fe36.png)
欧几里得算法
从上面的论述可以看出,要求一个数的逆元,实际上就是找到裴蜀定理
![equation?tex=ax%2Bby%3D%28a%2Cb%29%3D1](https://i-blog.csdnimg.cn/blog_migrate/d361df92e3488b4926fde0d9476448a9.png)
碾转相除法的依据是一个基本的事实:
![equation?tex=%28a%2Cb%29%3D%28a-kb%2Cb%29%2Ck+%5Cin+%5Cmathbb%7BZ%7D](https://i-blog.csdnimg.cn/blog_migrate/8328fbb6ea16bc2340c2514e4d0fdbf2.png)
这样,在 a > b时,令
![equation?tex=k%3D%5Clfloor+%5Cfrac%7Ba%7D%7Bb%7D+%5Crfloor](https://i-blog.csdnimg.cn/blog_migrate/9d4a1163d2eb1bb72228d1279049c2d8.png%3D%5Clfloor+%5Cfrac%7Ba%7D%7Bb%7D+%5Crfloor)
![equation?tex=%28a%2Cb%29%3D%28a-b%5Clfloor+%5Cfrac%7Ba%7D%7Bb%7D+%5Crfloor%2Cb%29%3D%28a%5Cbmod+b%2Cb%29%3D%28b%2C+a+%5Cbmod+b%29](https://i-blog.csdnimg.cn/blog_migrate/91675701a5b5a626098a17e816a3bcf4.png)
![equation?tex=b+%3E+%28a+%5Cbmod+b%29](https://i-blog.csdnimg.cn/blog_migrate/21c401ab0b33c1d544ead740a067782d.png)
![equation?tex=%28a%2C0%29%3Da](https://i-blog.csdnimg.cn/blog_migrate/c46c5d7c06e5fca5a306c78252f0c21f.png)
这种求最大公约数的算法叫做辗转相除法,又叫欧几里得算法。
根据上面的描述,很容易得出一个碾转相除法的递归实现:
int gcd(int a, int b) {
if(a < b) return gcd(b, a);
if(b == 0) return a;
return gcd(b, a % b);
}
对这个函数进行尾递归内联优化,就得到了它的迭代版本:
int gcd(int a, int b) {
if(a < b) return gcd(b, a);
while(b != 0) {
int t = a % b;
a = b; b = t;
}
return a;
}
辗转相除法是计算最大公约数很快的一种方式,但是它的潜力远不止此。下面我们要用它完成更加神奇的事情。
扩展欧几里得算法
欧几里得算法的另一个用途就是求解
![equation?tex=%28a%2Cb%29%3Dax%2Bby](https://i-blog.csdnimg.cn/blog_migrate/67f5023420e77ad1a1c7b3a98baf8e1f.png)
在欧几里得算法里,递归部分的核心是这样的:
![equation?tex=%28a%2Cb%29%3D%28b%2C+a+%5Cbmod+b%29%3D%28b%2C+a-b%5Clfloor+%5Cfrac%7Ba%7D%7Bb%7D+%5Crfloor%29](https://i-blog.csdnimg.cn/blog_migrate/354f23814d7b67e7fe026bba021a9a55.png)
根据裴蜀定理,我们可以找到四个整数
![equation?tex=x%2Cy%2Cx%27%2Cy%27](https://i-blog.csdnimg.cn/blog_migrate/0862a7b18a1d015fdf7cefcbc3e4dda2.png)
![equation?tex=ax%2Bby%3D%28a%2Cb%29](https://i-blog.csdnimg.cn/blog_migrate/ef18ed00721123f2008251d603d4de21.png)
![equation?tex=bx%27%2B+%28a-b%5Clfloor+%5Cfrac%7Ba%7D%7Bb%7D+%5Crfloor%29y%27%3D%28b%2C+a-b%5Clfloor+%5Cfrac%7Ba%7D%7Bb%7D+%5Crfloor%29](https://i-blog.csdnimg.cn/blog_migrate/3718f0c81eecb9d21f1945219ba2572e.png)
这两个等式的右侧是相等的,于是我们得到:
![equation?tex=ax%2Bby%3Dbx%27%2B+%28a-b%5Clfloor+%5Cfrac%7Ba%7D%7Bb%7D+%5Crfloor%29y%27](https://i-blog.csdnimg.cn/blog_migrate/f80cbce80f6a6c8d13d716d7a4d2cb1f.png)
![equation?tex=a%28x-y%27%29%2Bb%28y-%28x%27-%5Clfloor+%5Cfrac%7Ba%7D%7Bb%7D+%5Crfloor+y%27%29%29%3D0](https://i-blog.csdnimg.cn/blog_migrate/dc6100892f78764f6eeefc65b07e4d7e.png)
我们希望这个等式对一切 a,b 都成立,于是
![equation?tex=x%3Dy%27](https://i-blog.csdnimg.cn/blog_migrate/69060a6434b498071c4cbb639b200e45.png)
![equation?tex=y%3Dx%27-%5Clfloor+%5Cfrac%7Ba%7D%7Bb%7D+%5Crfloor+y%27](https://i-blog.csdnimg.cn/blog_migrate/cf1689e629f1ed367116348a1ff8f4ce.png)
这样,要求解 x 和 y,只需要求解 x',y',由于 x',y' 对应的问题规模更小,所以可以进行递归的运算。最后找一下边界条件:当 b=0 时,
![equation?tex=%28a%2C0%29](https://i-blog.csdnimg.cn/blog_migrate/af3aa19f1f50c8f209a2bba9f63767d0.png)
![equation?tex=x%3D1](https://i-blog.csdnimg.cn/blog_migrate/7dab9e989489d81b40a3a19164850ddb.png)
![equation?tex=y%3D0](https://i-blog.csdnimg.cn/blog_migrate/11ddbc4480efdee1e1a808a1674894e0.png)
这种算法的思想和欧几里得算法是一致的,而且它在求出最大公约数的同时,求解了一组适于裴蜀定理的系数,所以叫做扩展欧几里得算法。
递归算法也是很好写的:
int exgcd(int a, int b, int& x, int& y) {
if(a < b) return exgcd(b, a, y, x);
if(b == 0) {
x = 1; y = 0;
return a;
} else {
int x1;
int d = exgcd(b, a % b, x1, x);
y = x1 - a / b * x;
return d;
}
}
x 和 y 用引用的形式传入,返回值是最大公约数。
扩展欧几里得算法的迭代形式
有一个不太令人高兴的事实:扩展欧几里得算法的递归是在函数中央的,不能进行尾递归内联。
有句古话说得好:一时迭代一时爽,一直迭代一直爽。
![36db32e9a8bb83aa80d2962ea39e637b.png](https://i-blog.csdnimg.cn/blog_migrate/fd27748f477ad37a08f90077a53fd591.jpeg)
这点困难怎么能阻挡我们寻找迭代算法的脚步!
注意到,递归的每一步都是关于 x',y' 的线性组合,所以我们可以考虑用类似于线性递推数列的方式,用矩阵的形式改写递归式,从而利用矩阵乘法的结合律找到迭代算法。
这是我们找到的递归式:
![equation?tex=%5Cbegin%7Bcases%7D+x%3Dy%27%5C%5C%5B2ex%5D+y%3Dx%27-%5Clfloor+%5Cfrac%7Ba%7D%7Bb%7D+%5Crfloor+y%27+%5Cend%7Bcases%7D](https://i-blog.csdnimg.cn/blog_migrate/f4c1ff9d3006d64dc5c39ad304de2f47.png)
然后变成矩阵形式:
![equation?tex=%5Cbegin%7Bpmatrix%7Dx%5C%5Cy%5Cend%7Bpmatrix%7D%3D%5Cbegin%7Bpmatrix%7D0%261%5C%5C1%26-d_1%5Cend%7Bpmatrix%7D%5Cbegin%7Bpmatrix%7Dx%27%5C%5Cy%27%5Cend%7Bpmatrix%7D](https://i-blog.csdnimg.cn/blog_migrate/e4cb72a621e31696c73213e92df0c80c.png)
这里
![equation?tex=d_1](https://i-blog.csdnimg.cn/blog_migrate/8e4f0d212831daae9e3346db9dc03b2c.png)
![equation?tex=%5Clfloor+%5Cfrac%7Ba%7D%7Bb%7D+%5Crfloor](https://i-blog.csdnimg.cn/blog_migrate/899291c5662d141ea37833fc99489eed.png)
然后就可以一直乘下去:
![equation?tex=%5Cbegin%7Bpmatrix%7Dx%5C%5Cy%5Cend%7Bpmatrix%7D%3D%5Cbegin%7Bpmatrix%7D0%261%5C%5C1%26-d_1%5Cend%7Bpmatrix%7D%5Cbegin%7Bpmatrix%7D0%261%5C%5C1%26-d_2%5Cend%7Bpmatrix%7D%5Cbegin%7Bpmatrix%7D0%261%5C%5C1%26-d_3%5Cend%7Bpmatrix%7D%5Ccdots%5Cbegin%7Bpmatrix%7D0%261%5C%5C1%26-d_n%5Cend%7Bpmatrix%7D%5Cbegin%7Bpmatrix%7D1%5C%5C0%5Cend%7Bpmatrix%7D](https://i-blog.csdnimg.cn/blog_migrate/1f0a87f6980828248134088bbd55f261.png)
在每次迭代时,都可以从左向右计算一个矩阵,这样就达到了迭代的目的。
假设在一次迭代之前计算的矩阵之积为
![equation?tex=%5Cbegin%7Bpmatrix%7Dm_1%26m_2%5C%5Cn_1%26n_2%5Cend%7Bpmatrix%7D](https://i-blog.csdnimg.cn/blog_migrate/6ab6bcbbc56d1fac4c46c4ffdfa01de1.png)
![equation?tex=%5Cbegin%7Bpmatrix%7D0%261%5C%5C1%26-d_t%5Cend%7Bpmatrix%7D](https://i-blog.csdnimg.cn/blog_migrate/bd8d64909ca8e4432ead075ee5be5015.png)
![equation?tex=%5Cbegin%7Bpmatrix%7Dm_1%26m_2%5C%5Cn_1%26n_2%5Cend%7Bpmatrix%7D%5Cbegin%7Bpmatrix%7D0%261%5C%5C1%26-d_1%5Cend%7Bpmatrix%7D%3D%5Cbegin%7Bpmatrix%7Dm_2%26m_1-d_t+m_2%5C%5Cn_2%26n_1-d_t+n_2%5Cend%7Bpmatrix%7D](https://i-blog.csdnimg.cn/blog_migrate/6ab6bcbbc56d1fac4c46c4ffdfa01de1.png%5Cbegin%7Bpmatrix%7D0%261%5C%5C1%26-d_1%5Cend%7Bpmatrix%7D%3D%5Cbegin%7Bpmatrix%7Dm_2%26m_1-d_t+m_2%5C%5Cn_2%26n_1-d_t+n_2%5Cend%7Bpmatrix%7D)
把所有矩阵的积计算完成之后,得到一个最终的矩阵
![equation?tex=%5Cbegin%7Bpmatrix%7DM_1%26M_2%5C%5CN_1%26N_2%5Cend%7Bpmatrix%7D](https://i-blog.csdnimg.cn/blog_migrate/025b1e409ee80dfab41acb9af2f63d0d.png)
![equation?tex=%5Cbegin%7Bpmatrix%7Dx%5C%5Cy%5Cend%7Bpmatrix%7D%5Cbegin%7Bpmatrix%7DM_1%26M_2%5C%5CN_1%26N_2%5Cend%7Bpmatrix%7D%3D%5Cbegin%7Bpmatrix%7D1%5C%5C0%5Cend%7Bpmatrix%7D](https://i-blog.csdnimg.cn/blog_migrate/38e78ae5800c90ca51336baaa51f26ec.png)
![equation?tex=x%3DM_1](https://i-blog.csdnimg.cn/blog_migrate/aceb1bb5b6cddd18d8a7878f16c16e97.png)
![equation?tex=y%3DN_1](https://i-blog.csdnimg.cn/blog_migrate/701143a7a0e5a699720cc94ce9ee348c.png)
再看一看初始值,就是单位阵
![equation?tex=%5Cbegin%7Bpmatrix%7Dm_1%26m_2%5C%5Cn_1%26n_2%5Cend%7Bpmatrix%7D%3D%5Cbegin%7Bpmatrix%7D1%260%5C%5C0%261%5Cend%7Bpmatrix%7D](https://i-blog.csdnimg.cn/blog_migrate/6ab6bcbbc56d1fac4c46c4ffdfa01de1.png%3D%5Cbegin%7Bpmatrix%7D1%260%5C%5C0%261%5Cend%7Bpmatrix%7D)
所以这里可以有一个小(负)优化,在迭代时直接把矩阵写成
![equation?tex=%5Cbegin%7Bpmatrix%7Dx%26m_2%5C%5Cy%26n_2%5Cend%7Bpmatrix%7D](https://i-blog.csdnimg.cn/blog_migrate/e521066bf9ca758d95b95c24458d8d07.png)
现在梳理一下思路:
- 第一步是给矩阵赋初始值。
- 然后开始迭代。每一次迭代时,计算
。
- 更新矩阵,把矩阵右乘迭代矩阵的结果赋值给原先的矩阵。
- 进行一般欧几里得算法的迭代。
- 当 b = 0 时,计算结束,此时 x 和 y 已经计算完成,并且最大公约数储存在 a 中。
给一个迭代版本的代码:
int exgcd(int a, int b, int& x, int& y) {
if(a < b) return exgcd(b, a, y, x);
int m = 0, n = 1;
x = 1; y = 0;
while(b != 0) {
int d = a / b, t;
t = m; m = x - d * t; x = t;
t = n; n = y - d * t; y = t;
t = a % b; a = b; b = t;
}
return a;
}
扩展欧几里得算法与乘法逆元
当我们用扩展欧几里得算法找到一组 x 和 y 满足
![equation?tex=ax%2Bby%3D1](https://i-blog.csdnimg.cn/blog_migrate/8575f617492fd9cc03055f66a388e178.png)
![equation?tex=ax%5Cequiv+1+%5Cpmod%7Bb%7D](https://i-blog.csdnimg.cn/blog_migrate/0427a99dac9bbe99b844652411fe420a.png)
显然,如果 x 是 a 的乘法逆元,那么所有的
![equation?tex=x%2Bkb%2C+k+%5Cin+%5Cmathbb%7BN%7D](https://i-blog.csdnimg.cn/blog_migrate/7ed42fca28cc6ebca4fdefad14fd1036.png)
![equation?tex=%5B0%2Cb%29](https://i-blog.csdnimg.cn/blog_migrate/06f1c6708e87174215cc4361ed1c4927.png)
这是找到 n 在模 p 下的乘法逆元的一个算法,使用了上面的 exgcd:
int inv(int n, int p) {
int x, y;
if(exgcd(n, p, x, y) == 1) {
x = x % p;
return x >= 0 ? x : p + x;
} else {
return -1;
}
}
如果返回 -1,表示乘法逆元不存在。
(封面pid:72665414)