1.3 GCD、EXGCD、线性同余方程
最大公约数(GCD)和最小公倍数(LCM)的基本概念也是小学知识。对于任意整数 ,我们有一个很重要的公式,即 。
首先我们会介绍几种基本的求GCD方式,虽然C++选手完全可以使用__gcd()函数去求,但自定义的求法往往更加灵活,方便优化以及改动实现其他功能。
辗转相除法求GCD
辗转相除法又称欧几里得算法,其原理是 ,证明如下:
①设 , ,则 。
②设 不是 因子,则 不是 , 的公因子。
③设 , 不是 的因子,则 不是 , 的公因子。
当然我们可以把这个转换为模运算,因为取 的前提是 ,于是我们可以递归的用模运算去实现,保证函数的第一个参数大于第二个参数(实在理解不了可以记模板)。代码如下。
typedef long long ll;
ll GCD(ll x, ll y) {
return y == 0 ? x : GCD(y,x % y);
}
二进制算法求GCD
这个算法比辗转相除法更快一点点,但这个优化是常数级别的。原理是通过不断去除因子2来降低常数。具体做法如下:
若 ,则 ,否则:
①若 均为偶数,则 ;
②若 为偶数, 为奇数,则 ;
③若 为奇数, 为偶数,则 ;
④若 均为奇数,则用欧几里得算法求,即
代码如下:
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
inline ll GCD(ll x, ll y) {
if (x == 0) return y;
if (y == 0) return x;
int i, j;
for (i = 0; (x & 1) == 0; ++i) x >>= 1;//x去2
for (j = 0; (y & 1) == 0; ++j) y >>= 1;//y去2
i = min(i, j);
while (1) {
if (x < y)
swap(x, y);
if ((x -= y) == 0) return y << i; // x = y时返回结果
while ((x & 1) == 0) x >>= 1;//x去2
}
}
这个代码还是非常值得细看的,对于理解这个方法有好处,至于为什么不用简单好写的递归,显然递归会爆栈,MLE警告。
最小公倍数LCM
除了最前面介绍的 去求LCM以外,还以可以考虑逐步倍增法,比如求3和8的LCM,可以从1开始递增,每次检查 是不是3的倍数,当 时成立,所以
扩展欧几里得算法(EXGCD)
用途:已经一组 ,求解一组 ,使得 。这个解是一定存在的。
该算法能返回 。
因为
,所以
那么由此我们可以得出EXGCD算法的实质:通过欧几里得算法辗转求出GCD的同时,利用上面公式得出的性质。递归回溯地求出最初的 的值,而递归出口为当b减小至0时,一定满足 。而每一层p等于下一层的q,每一层的q等于下一层的 。
那么我们来看看EXGCD的代码
#include <iostream>
using namespace std;
typedef long long ll;
//代码中用x,y代替原题目中的p,q
ll exgcd(ll a, ll b, ll &x, ll &y) {
ll ret, tmp;
if (!b) {
x = 1;
y = 0;
return a;
}
ret = exgcd(b, a % b, x, y);
tmp = x;
x = y;
y = tmp - a / b * y;
return ret;
}
int main() {
ll a, b, x, y, z;
cin >> a >> b;
z = exgcd(a, b, x, y);
cout << z << ' ' << x << ' ' << y;
return 0;
}
线性同余方程
首先给出两个定理:
①对于方程 ,该方程等价于 ,有整数解的充要条件是: 。
根据这条定理①,对于方程 ,我们可以用EXGCD算法求出一组 ,然后两边同时除以 后再乘以c,即可得出原方程的解。
②若 ,且 为方程 的一组解,则该方程的任意解可表示为: ,对于任何整数t皆成立。
根据定理2可以求出方程所有的解,但实际问题中我们一般会去求一个最小整数解,即一个特解x,此时 , 。
实现代码如下:
#include <iostream>
using namespace std;
typedef long long ll;
ll exgcd(ll a, ll b, ll &x, ll &y) {
ll ret, tmp;
if (!b) {
x = 1;
y = 0;
return a;
}
ret = exgcd(b, a % b, x, y);
tmp = x;
x = y;
y = tmp - a / b * y;
return ret;
}
int main() {
ll a, b, x, y, z;
cin >> a >> b;
z = exgcd(a, b, x, y);
ll t = b / z;
x = (x % t + t) % t;
cout << x;
return 0;
}
对应例题:luogu-1082同余方程
推荐题目:POJ-1061青蛙的约会