cqyz oj | 扩展欧几里得算法 | Exgcd

Description

  欧几里德算法又称辗转相除法,是指用于计算两个正整数a,b的最大公约数。应用领域有数学和计算机两个方面。计算公式

 

   扩展欧几里德算法是用来在已知a, b,求解一组 x,y 使得:ax + by = gcd(p,q) (解一定存在,根据数论中的相关定理)。

  本题任务:给定任意整数a,b,c,求方程 ax + by = c 的整数解,输出其中x是最小的正整数的一组。

Input

  若干行,每行一组数据:a,b,c。他们都是int范围内的整数。

Output

  每组数据输出一行,若给定方程没有整数解,输出"No solution",否则输出x是最小的正整数的一组。

Sample Input 1 

1 1 1
1 2 1000
12 20 7
27 -45 18
6 15 9

Sample Output 1

1 0
2 499
No solution
4 2
4 -1

Hint

a,b,c在int范围内,且都不为0。


 

1.理论部分

欧几里得算法求最大公因数:

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

其中模运算可定义为:(以下除法都是指下取整除法)

a%b = a-a/b*b

 

扩展欧几里得算法:已知整数a、b,扩展欧几里得算法可以在求得a、b的最大公约数的同时,能找到整数x、y(其中一个很可能是负数),使它们满足贝祖等式

 

推导:由欧几里得算法:

ax + by = g //g=gcd(a,b)
==> bx' + (a%b)y' = g
==> bx' + (a-a/b*b)y' = g
==> bx' + ay' - a/b*by' = g
==> ay' + b(x'-a/b*y') = g
==> x=y',  y=x'-a/b*y'

这样可以迭代求解,通过调用gcd(b, a%b)得到的x'和y'算出当前的x和y

由上述推导可直接写出如下的扩展欧几里得算法:

int Exgcd(int a, int b, int &x, int &y) {
    if(b == 0) { x=1, y=0; return a; } // gcd=a => gcd*1+0*y=a => x=1, y任取 
    int xx, yy;
    int gcd = Exgcd(b, a%b, xx, yy);
    x = yy;
    y = xx - a / b * yy;
    return gcd;
}

可以不用xx和yy,代码稍加化简得到:

int Exgcd(int a, int b, int &x, int &y) {
    if(b == 0) { x=1; return a; }
    int gcd = Exgcd(b, a%b, y, x);
    y = y - a / b * x;
    return gcd;
}

 

通过调用gcd = Exgcd(a, b, x, y),可得到一组满足ax+by=gcd的x和y的特解。

 

根据裴蜀定理,这样的x,y应该有无数对。

已知一对特解x0,y0,可由如下方式得到通解:

x = x0 + b / gcd * k

y = y0 - a / gcd * k(k属于整数)

证明:将x, y代入方程:

ax + by

= a(x0 + b / gcd * k) + b(y0 - a / gcd * k)

= a*x0 + b*y0 = gcd(a, b)

成立,证毕.

为了找到尽量多的通解,每次转移的步长应该尽量短,故式中a、b要/gcd

 

2.实践部分

利用扩展欧几里得算法实现本题:

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;

LL Exgcd(LL a, LL b, LL &x, LL &y) {
    if(b == 0) { x=1, y=0; return a; } // gcd=a => gcd*1+0*y=a => x=1, y任取 
    LL gcd = Exgcd(b, a%b, y, x);
    y = y-a/b*x;
    return gcd;
}

int main() {
    LL a, b, c, x, y, gcd;
    while(scanf("%lld%lld%lld", &a, &b, &c) == 3) {
        gcd = Exgcd(a, b, x, y); // 得到满足ax+by=gcd(a, b)的一组x, y 
        if(c%gcd != 0) { puts("No solution"); continue; }
        a/=gcd, b/=gcd, c/=gcd; // 方程两边约分使得a, b互质gcd=1 
        if(b<0) a=-a, b=-b, c=-c, x=-x, y=-y; // 保证b为正,下面x0求出来才为正 
        // 方程改为(-a)x+(-b)y=(-c)
        // x,y符号不变,但为了使下一步算x0的x*c符号总体不变此处x,y符号改变 
        LL x0 = x*c; // x是ax+by=1的一个x, 所以x0=x*c是ax+by=c的一个x
        x0 = (x0 % b + b) % b; // ax+by=1 的通解: x=x0+k*b , y=y0-k*a, 代入方程展开即证 
        if(x0 == 0) x0 = b; // 保证为最小正整数 
        LL y0 = (c-a*x0)/b; // 确定了x0求y0 
        printf("%d %d\n", (int)x0, (int)y0);
    }
    return 0;
}

-

posted @ 2020-01-18 17:15  Deguassing-compass  阅读(233)  评论(0编辑  收藏  举报