扩展欧几里得(及其相关证明)
仅看证明可移步到 扩展欧几里得(新) - blind5883 - 博客园 (cnblogs.com)。
下方含逆元概念。
/* "数学的恐怖qwq" 想了半天终于明白了, 这里尽量通俗的写出来 扩展欧几里得算法有很多版本 这里写两个, 选择喜欢的使用 扩欧可以解决两项未知解, 具体原理来自裴蜀定理 裴蜀定理:设 a,b 是不全为零的整数, 则存在整数 x,y, 使得 ax+by=gcd(a,b). 而扩欧就可以在求出gcd的过程中把其中的x和y求出来(一种方案) 就可以得到式子ax + by = gcd(a, b);中所有的数值 因为知道了gcd(a, b)就可以通过这样求出每个二元一次方程组的一个解 例如已知a, b, c, 求满足ax + by = c中想x, y的一个解 通过扩欧可以求出a*x1 + b*y1 = gcd(a, b); 这个式子左右两边都乘上c / gcd(a, b); 就可以得到a*x2 + b*y2 = c; 这时候x2和y2我们知道, 而这就是ax + by = c的一个解 通过这个还可以求逆元 而具体实现 普通欧几里得如下 int gcd(int a, int b) { return b ? gcd(b, a % b) : a; } 或者应该这样 int gcd(int a, int b) { if (b == 0) { return a; } int d = gcd(b, a % b); return d; } 我们可以多带两个值x, y, 因为递归下一层内的数组没法直接传上来, 这里就通过引用的方式, 取址传上来, 不是取值 就变成 int ecgcd(int a, int b, int &x, int &y) { if (b == 0) { return a; } int d = ecgcd(b, a % b, ?, ?); // 这两个问号下面会补上 return d; } 那俩问号暂且不管 那么问题来了, 怎么求x, y, 首先你要知道, 只有一个等式是无法求出二元一次方程的唯一解的 所以我们的解有无穷个, 而对于最后终止的那里, 也就是b == 0的情况 ax + by = d, 因为b == 0, 所以a = d(为什么见普通欧几里得), 所以x = 1, 这样就确定了x, 但是y是可以变化的, 这也是为什么有无数解的原因 因此这里的y可以随便去一个, 通常选y = 0(为什么下面会说), // 请把所有的x和y看成未知数, 不要想位置关系, 这里只是为了方便 那么终止的x, y选完了, 接着就是看看怎么把x和y带回去 原式子是ax + by = d; (1) 它的递归下一层就是b*y1 + a%b * x1 == d (2) a % b == a - a/b * b (3) 由(1)(3)可得 (a%b + a/b * b)*x + by = d (4) 化简得 a%b * x + b(y + a/b * x) = d (5) 通过(2)和(5) 可以发现, x1 = x, y1 = y + a/b * b (6) y = y1 - a/b * x, x = x1; (7) 而x1和y1就是我想通过x和y传回来的值, 这时候看看上面两个问号, 就有两种填法 ecgcd(b, a % b, x, y) 和 ecgcd(b, a % b, y, x) 如果是ecgcd(b, a % b, x, y) 那么本次递归ax + by = d, 下次b * x2 + a%b * y2 = d; 注意这里和(2)不一样 传回来的x = x2 = y1, y = y2 = x1; 注意这里x和y数值上是下一层递归里面传上来的 根据 y = y1 - a/b * x, x = x1; (7) 可得 本次递归里的y = 下个递归里的x2 - 本次的a/b * 本次的x, 本次的x = 下次的y2 而在实现上就是int t = y; y = x - a / b * t; x = t; 这里有点难懂, 要注意x和y数值上是下一层递归里面传上来的, 用的还是本次递归的x, y 作为容器, 我们要利用x, y, 更改他们自己 具体代码就是 int exgcd(int a, int b, int &x, int &y) { if (b == 0) { x = 1; y = 0; return a; } int d = exgcd(b, a % b, x, y); int t = y; y = x - a / b * t; x = t; return d; } 如果是如果是ecgcd(b, a % b, y, x) 那么本次递归ax + by = d, 下次b * y3 + a%b * x3 = d; // 这里的x3 y3都是未知数, 没什么具体含义, 所有的关系就是下面的那一句话 传回来的y = y3 = y1, x = x3 = x1; 根据 y = y1 - a/b * x, x = x1; (7) 可得 本次的y = 下次的y3 - 本次的a/b * 本次的x, 本次的x = 下次的x3 实现就是 y = y - a / b * x, x = x ==> y -= a / b * x; 唉?这时候我们传上来的x恰好等于这次的x, x就不用动了 y的话就相当于自减, 非常巧妙 实现 int exgcd(int a, int b, int &x, int &y) { if (b == 0) { x = 1; y = 0; return a; } int d = exgcd(b, a % b, y, x); y -= a / b * x; return d; } 到这里就解释的差不多了, 注意=的关系, b == 0时最好y = 0, 因为我们会把y传回去, 他会经历一系列变化 取0的话可以防止出现int爆炸的情况, 不然你可以试试这题y = 100 至此此题结束 */ #include <iostream> #include <algorithm> #include <cstring> using namespace std; int n, m; // int exgcd(int a, int b, int &x, int &y) // { // if (b == 0) // { // x = 1; // y = 0; // return a; // } // int d = exgcd(b, a % b, x, y); // int t = y; // y = x - a / b * t; // x = t; // return d; // } int exgcd(int a, int b, int &x, int &y) { if (b == 0) { x = 1; y = 0; return a; } int d = exgcd(b, a % b, y, x); y -= a / b * x; return d; } int main() { int T; cin >> T; while (T -- ) { int x, y; scanf("%d%d", &n, &m); exgcd(n, m, x, y); printf("%d %d\n", x, y); } return 0; }
求逆元
什么是逆元, 逆元就是形如 a * x ≡ 1 (mod p)
其中x就叫做a
对于p
的一个逆元
相对的a
就是x
关于p
的一个逆元
知道了定义 a * x ≡ 1 (mod p)
这个式子我们可以把他展开
变成a * x + p * y = 1 (y是任意整数)
这个式子形如 ax + by = d
所以这个式子可以用exgcd进行求解
也就是求出里面的x
求逆元还有一种方法, 也是一种特殊的情况, 当且仅当p
为质数且(a, p) = 1
这时候它满足费马小定理 :如果p是一个质数,而整数a不是p的倍数,
也就是当且仅当p
为质数且(a, p) = 1
, 则有a ^ (p - 1) ≡ 1 (mod p)
这时候x
也就是a
的逆元就是a ^ (p - 2)
,
而这个东西可以通过快速幂求解
这就是逆元的特殊求法。
注意一点,如果
欧拉定理 & 费马小定理 - OI Wiki (oi-wiki.org)
[[ecgcd通解]]
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】