1.3 GCD、EXGCD、线性同余方程

1.3 GCD、EXGCD、线性同余方程

最大公约数(GCD)和最小公倍数(LCM)的基本概念也是小学知识。对于任意整数 a , b a,b ,我们有一个很重要的公式,即 a b = G C D ( a , b ) L C M ( a , b ) a*b = GCD(a,b)*LCM(a,b)

首先我们会介绍几种基本的求GCD方式,虽然C++选手完全可以使用__gcd()函数去求,但自定义的求法往往更加灵活,方便优化以及改动实现其他功能。

辗转相除法求GCD

辗转相除法又称欧几里得算法,其原理是 G C D ( x , y ) = G C D ( x , y x ) GCD(x,y)=GCD(x,y-x) ,证明如下:

①设 z x z|x z y z|y ,则 z ( y x ) z|(y-x)

②设 z z 不是 x x 因子,则 z z 不是 x x y x y-x 的公因子。

③设 z x z|x z z 不是 y y 的因子,则 z z 不是 x x y x y-x 的公因子。

当然我们可以把这个转换为模运算,因为取 y x y-x 的前提是 y > x y>x ,于是我们可以递归的用模运算去实现,保证函数的第一个参数大于第二个参数(实在理解不了可以记模板)。代码如下。

typedef long long ll;
ll GCD(ll x, ll y) {
    return y == 0 ? x : GCD(y,x % y);
}

二进制算法求GCD

这个算法比辗转相除法更快一点点,但这个优化是常数级别的。原理是通过不断去除因子2来降低常数。具体做法如下:

x = y x=y ,则 G C D ( x , y ) = x GCD(x,y)=x ,否则:

①若 x , y x,y 均为偶数,则 G C D ( x , y ) = 2 G C D ( x / 2 , y / 2 ) GCD(x,y) = 2*GCD(x/2,y/2) ;

②若 x x 为偶数, y y 为奇数,则 G C D ( x , y ) = G C D ( x / 2 , y ) GCD(x,y)=GCD(x/2,y) ;

③若 x x 为奇数, y y 为偶数,则 G C D ( x , y ) = G C D ( x , y / 2 ) GCD(x,y)=GCD(x,y/2) ;

④若 x , y x,y 均为奇数,则用欧几里得算法求,即 G C D ( x , y ) = G C D ( x y , y ) GCD(x,y) = GCD(x-y,y)

代码如下:

#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

除了最前面介绍的 a b = G C D ( a , b ) L C M ( a , b ) a*b = GCD(a,b)*LCM(a,b) 去求LCM以外,还以可以考虑逐步倍增法,比如求3和8的LCM,可以从1开始递增,每次检查 8 n 8*n 是不是3的倍数,当 n = 3 n=3 时成立,所以 L C M = 3 8 = 24 LCM = 3 * 8 = 24

扩展欧几里得算法(EXGCD)

用途:已经一组 a , b a,b ,求解一组 p , q p,q ,使得 p a + q b = G C D ( a , b ) p*a+q*b=GCD(a,b) 。这个解是一定存在的。

该算法能返回 p , q , G C D ( a , b ) p,q,GCD(a,b)

因为 G C D ( a , b ) = G C D ( b , a   %   b ) GCD(a,b) = GCD(b,a\ \%\ b) ,所以
p q + q b = G C D ( a , b ) = G C D ( b , a   %   b ) = p b + q a   %   b = p b + q ( a a / b b ) = q a + ( p a / b q ) b p*q+q*b=GCD(a,b)=GCD(b,a\ \%\ b)=p*b+q*a\ \%\ b=p*b+q*(a-a/b*b)=q*a+(p-a/b*q)*b

那么由此我们可以得出EXGCD算法的实质:通过欧几里得算法辗转求出GCD的同时,利用上面公式得出的性质。递归回溯地求出最初的 p , q p,q 的值,而递归出口为当b减小至0时,一定满足 p = 1 , q = 0 p=1,q=0 。而每一层p等于下一层的q,每一层的q等于下一层的 p a / b q p-a/b*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;
}

线性同余方程

首先给出两个定理:

①对于方程 a x + b y = c a*x+b*y=c ,该方程等价于 a x c ( m o d   b ) a*x\equiv c(mod\ b) ,有整数解的充要条件是: c   %   G C D ( a , b ) = 0 c\ \%\ GCD(a,b)=0

根据这条定理①,对于方程 a x + b y = c a*x+b*y=c ,我们可以用EXGCD算法求出一组 x 0 , y 0 x_0,y_0 ,然后两边同时除以 G C D ( a , b ) GCD(a,b) 后再乘以c,即可得出原方程的解。

②若 G C D ( a , b ) = 1 GCD(a,b)=1 ,且 x 0 , y 0 x_0,y_0 为方程 a x + b y = c a*x+b*y=c 的一组解,则该方程的任意解可表示为: x = x 0 + b t , y = y 0 a t x=x_0+b*t,y=y_0-a*t ,对于任何整数t皆成立。

根据定理2可以求出方程所有的解,但实际问题中我们一般会去求一个最小整数解,即一个特解x,此时 t = b / G C D ( a , b ) t=b/GCD(a,b) x = ( x   %   t + t ) % t x=(x\ \%\ t+t)\%t

实现代码如下:

#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青蛙的约会

发布了2 篇原创文章 · 获赞 4 · 访问量 121

猜你喜欢

转载自blog.csdn.net/avgstuBoboge/article/details/104095351
1.3
今日推荐