欧几里德 和 拓展欧几里德算法

一.欧几里德

欧几里德是用来求最大公约数的算法
其算法的中心思想为:

设a,b的最大公约数为c,则c一定也是a%b的最大公约数

证明方法很多,下面列出最简单的一个:
令 :a = xc; b = yc ; a = bk + r

(1) 证明 c 为 r 的约数

$r = a - b*k = xc - ykc = c(x - ky) $
故 c 为 r 的一个约数,证毕

(2) 证明 c 为 b和r的最大公约数

b = y ∗ c b = y*c b=yc
r = ( x − k y ) ∗ c r = (x - ky)*c r=(xky)c
故问题转化为 : 证明 y 和 (x - ky) 互质

我们可以使用反证法:
假设y 和 (x - ky)存在公因子 t
y = p t y = pt y=pt
x − k y = q t x - ky = qt xky=qt
则 $x = ky + qt = kpt + qt = (kp + q) * t $

则说明 t 也是 x,y的公因子
但是
由初始定义:
a = x ∗ c a = x*c a=xc
b = y ∗ c b = y*c b=yc
若x,y存在公因子t,则 c*t 才为 a,b的最大公因子,则与初始条件 c 为 a,b的最大公因子矛盾。
故假设不成立。
即 y 和 (x - ky) 互质
因为

b = yc
r = (x-k
y)*c

故 c 为 b,r的最大公约数,证毕

设gcd(a,b)为a,b,的最大公约数,则
==> gcd(a,b) = gcd(b,r) = gcd(b,a%b)

代码:

int gcd(int a,int b){
    if(b == 0) return a;
    return gcd(b,a%b);
}

二.拓展欧几里德

已知一个问题:

已知a,b,c均为整数 问对于ax + by = c来说是否存在整数解,如果存在则求出任意一组整数解

此问题我第一次是见于HDU的校赛,不过只是需要求解是否存在(原题大概是问一个三元一次方程ax + by + cz = d,在a,b,c,d确定的情况下存在多少组解)
此题正解是背包,而我是通过移项枚举z的值,然后快速判断二元一次方程是否存在解来判断方案数量。

但当时没学拓欧,就各种乱搞WA了8次,最后YY出了一个快速判断的公式,终于AC。
后来才知道,其实跟拓欧有着很大的联系。

回到开始的问题:

(一)对于判断是否有整数解,其实很简单,就判断一下:

c % gcd(a,b) == 0 是否成立
其实这是很容易想通的,令:
r = g c d ( a , b ) r = gcd(a,b) r=gcd(a,b)
则无论如果改变x,y的大小,对于ax + by 每次改变的值一定是r的倍数,因为
(a+b) ,(a-b), a, b,a+2b,a - 2b…都是r的倍数

如果c 不是 r的倍数,则一定不存在整数解。

(二)至于某一组x,y的值:

则可以用拓展欧几里德来求解:
首先考虑特殊情况:
a ∗ 1 + b ∗ 0 = g c d ( a , b ) = a a*1 + b*0 = gcd(a,b) = a a1+b0=gcd(a,b)=a

则我们可以考虑从已知的特殊情况推导到未知情况

由欧几里德定理,我们知道:
g c d ( a , b ) = = g c d ( b , a % b ) gcd(a,b) == gcd(b,a\%b) gcd(a,b)==gcd(b,a%b)

故我们可以考虑下面这种情况,将 a , b a,b a,b分别用 b b b ( a % b ) (a\%b) (a%b)代替,得:

(1) b x + ( a % b ) y = g c d ( a , b ) bx + (a\%b)y = gcd(a,b)\tag1 bx+(a%b)y=gcd(a,b)(1)

这一定是一个满足有解条件的合法等式。
假设上式得 x , y x,y x,y我们已经知道值是多少了,我们试着将该式转化为这种形式:
a x ′ + b y ′ = g c d ( a , b ) ax' + by' = gcd(a,b) ax+by=gcd(a,b)

x ′ , y ′ x',y' x,y能够用x,y进行表示,那我们就达到了从已知情况推导到未知情况的目的。

借用一个显然的条件来化简: a % b = a − ( a / b ) ∗ b a \% b = a - (a/b) * b% a%b=a(a/b)b

代入(1)式化简得:
(2) a y + b ( x − ( a / b ) y ) = g c d ( a , b ) a y + b (x - (a/b)y) = gcd(a,b)\tag2 ay+b(x(a/b)y)=gcd(a,b)(2)


x ′ = y x' = y x=y
y ′ = x − ( a / b ) ∗ b y' = x - (a/b)*b y=x(a/b)b

所以为什么叫拓展欧几里德呢?因为该算法是脱胎于欧几里德算法。
我们要由已知情况推导到未知情况来解决问题
推导方法就是利用欧几里德算法用a,b的变化来带动x,y的变化

代码1:

int exgcd(int a,int b,int &x,int &y){
    int d = a;
    if(b != 0){
        d = exgcd(b,a%b,y,x);
        y -= (a/b)*x;
    }
    else{
        x = 1,y = 0;
    }
    return d;
}

代码2:(更易理解版)

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);  //通过变化a,b一直往下递归,直到达到已知的特殊情况,然后返回
    int t = x;                
    x = y,y = t - (a/b)*y;      //代入公式。
    return d;
}

(三)求最小整数解or解的间隔:

先上结论,解的间隔:
d y = a / g c d ( a , b ) dy = a/gcd(a,b) dy=a/gcd(a,b) d x = b / g c d ( a , b ) dx = b/gcd(a,b) dx=b/gcd(a,b)
x x x的最小整数解为:
( x 0 % d x + d x ) % d x (x_0 \% dx + dx) \% dx (x0%dx+dx)%dx (考虑负模的情况)

证明详见:证明链接

int solve(int a,int b,int c){
    int x,y;
    int d = exgcd(a,b,x,y);
    if(c%d) return -1; //无解

	x*=c/d;            //解出的x满足 ax+by = gcd(a,b)
					   //而我们要的是 ax'+by' = c
					   //因c 是 gcd(a,b)的倍数,等式两端同时乘上c/gcd(a,b),x'即为所求。
    b/=d;b=abs(b);     //dx = b/d,考虑负模的情况。
    return (x%b+b)%b;  //返回最小正整数解
}

经典题

题目链接

( A / B ) m o d    m (A/B)\mod m A/Bmodm
最直接的做法可以转化为:
A m o d    ( B ∗ m ) / B A \mod (B*m) / B Amod(Bm)/B

但当B,m均很大时B*m或许会溢出(当然会Java就不怕啦= =),
或像该题不直接把A告诉你时,我们可以利用拓欧来解决该问题。
推导:

( A / B ) % 9973 = x (A/B)\% 9973 = x A/B%9973=x
( A / B ) = 9973 ∗ k + x ( k 为 整 数 ) (A/B) = 9973*k + x (k为整数) A/B=9973k+x(k
所以 A = 9973 ∗ k ∗ B + x ∗ B A = 9973*k*B + x*B A=9973kB+xB

因为 A % 9973 = n A \% 9973 = n A%9973=n
( x ∗ B ) % 9973 = n (x * B) \% 9973 = n xB%9973=n

所以 B ∗ x = 9973 ∗ k + n B*x = 9973*k + n Bx=9973k+n
B ∗ x − 9973 k = n B*x - 9973k = n Bx9973k=n
此时$ x,k$为未知数,且 g c d ( B , 9973 ) = = 1 gcd(B,9973) == 1 gcd(B,9973)==1(题目条件)故一定有解
这样便转化成了 拓展欧几里德的水题

代码:

#include
using namespace std;
typedef long long ll;

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 = x;
    x = y,y = t - (a/b)*y;
    return d;
}

int solve(int a,int b,int c){
    int x,y;
    exgcd(a,b,x,y);
    return ((x*c)%b + b) % b;
}

int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        int n,B;
        scanf("%d%d",&n,&B);
        int ans = solve(B,9973,n);
        printf("%d\n",ans);
    }
    return 0;
}

你可能感兴趣的:(欧几里德 和 拓展欧几里德算法)