数论补完计划 Part2 欧几里得算法
欧几里得算法
众所周知的是,欧几里得算法可以用来求解最大公约数。它的核心是一个恒等式 __gcd() 来求解最大公约数,而在 C++17 以后,我们可以使用 gcd() 和 lcm() 函数来求解两个数的最大公约数和最小公倍数。
通常我们采用递归版本:
int gcd(int a, int b) { return b ? gcd(b, a % b) : a;}当然,C++ 标准使用的是循环版本:
int gcd(int a, int b) { while (b) { int t = a % b; a = b; b = t; } return a;}扩展欧几里得算法
今天的重头戏是我们的扩展欧几里得算法(简称“扩欧”)。它和一般的朴素-扩展定理不太一样,按照常理,应该是一个针对互质(朴素)、一个针对不互质(扩展)。但是欧几里得算法并不存在互质和不互质的差别,因而也就没有这样的区分。扩展欧几里得算法能够在求解最大公约数的基础上连带求出不定方程
贝祖定理
也就是
对于任意不全为零的整数
,总存在整数 ,使得 成立
我们可以利用这个定理来判断不定方程解的情况。
证明:
当
当
退出时
因此在方程
扩展欧几里得
假如我们拿到一个方程
把取模换成带余除法,
即,
发现这个式子和最开始的那个是同型的。也就是说我们每次递归的时候都需要把
借助 C++ 的引用特性,可以在每次递归时计算。
int exgcd(int a, int b, int &x, int &y) { if (!b) { y = 0, x = 1; return a; } int d = exgcd(b, a % b, y, x); y -= (a / b) * x; return d;}不定方程的通解
我们都知道,通过扩展欧几里得算法求出的
对于一个二元一次不定方程的基本式
一组特解为
也因此,
我们一般只考虑整数解,所以需要保证
题目中还会给你一些限制条件,例如
假如限制条件是,
解得:
因为
当右边严格小于左边时,无解,即没有
#include <bits/stdc++.h>using namespace std;
typedef long long ll;
ll exgcd(ll a, ll b, ll &x, ll &y) { if (!b) { x = 1, y = 0; return a; } ll d = exgcd(b, a % b, y, x); y -= a / b * x; return d;}
int main() { ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
int t; cin >> t; while (t--) { ll a, b, c, x, y; cin >> a >> b >> c; if (c % gcd(a, b) == 0) { ll gcd = exgcd(a, b, x, y); x *= c / gcd, y *= c / gcd; // 转化为推导式 ll low = ceil((1.0 - x) * gcd / b); // 算下界,浮点数1.0是必须的 ll up = floor((y - 1.0) * gcd / a); // 上界 if (up < low) { cout << (x + low * b / gcd) << ' ' << (y - up * a / gcd) << endl; // x,y 最小值 } else { cout << up - low + 1 << ' '; // 区间大小 cout << (x + low * b / gcd) << ' ' << (y - up * a / gcd) << ' '; // x,y 最小值 cout << (x + up * b / gcd) << ' ' << (y - low * a / gcd) << endl;// x,y 最大值 } } else cout << -1 << endl; } return 0;}乘法逆元
乘法逆元其实就是模意义下的倒数,因为取模是针对整数而言的,而实数域的倒数可能不是一个整数,所以引入一个乘法逆元的概念。定义线性同余方程
根据定义,我们其实就可以直接开始用扩展欧几里得计算了。特殊情况下,当
根据带余除法,我们把同余方程变成这样:(x % MOD + MOD) % MOD 来将它转化为非负数。
调用 exgcd(a, p, x, y) 所得
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!
部分内容可能已过时