扩展欧几里得

以前模模糊糊地学了扩欧,总是感觉不系统,今天好好整理了一下,应该算是把扩欧给弄透。

首先要先明白gcd(最大公约数),很重要的是辗转相除法,即gcd(a,b)=gcd(b,a%b)

板子是这样

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

然后就是扩欧

根据裴蜀定理:

ax + by = m

有解当且仅当m是gcd的倍数。裴蜀等式有解时必然有无穷多个整数解,每组解x、y都称为裴蜀数,可用辗转相除法求得。

所以板子如下

int exgcd(int a,int &x1,int b,int &y1)
{
    if(b==0) 
    {
        x1=1;
        y1=0;
        return a;
    }
    int x2,y2;
    int gcd=exgcd(b,x2,a%b,y2);
    x1=y2;
    y1=x2-a/b*y2;
    return gcd;
}

注意是&x和&y,参数的值是改变了的,最后求出的x,y的值实际上是ax+by=gcd(a,b)的解,且满足|x+y|最小

而对于要求的方程ax+by=c

若gcd|c则有解,不然无解,加一个判断就好

if(c%d) printf("No\n");

而真正的x应该是x*(c/gcd),y应该是y*(c/gcd)

如果要求x或y的最小非负解该肿么办呢?

我们知道:ax+by=c,那么a(x+(b/gcd)*k)+b(y-(a/gcd)*k)=c

x,y的周期分别是b/gcd,a/gcd

所以在%的时候分别%一个b/gcd,a/gcd

下面是完整代码(要求求ax+by=c的x,y的最小非负解)

#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll exgcd(ll a,ll &x1,ll b,ll &y1)
{
    if(b==0){y1=0;x1=1;return a;}
    ll x2,y2;
    ll gcd=exgcd(b,x2,a%b,y2);
    x1=y2;
    y1=x2-a/b*y2;
    return gcd;
}
int main()
{
    freopen("modeq.in","r",stdin);
    freopen("modeq.out","w",stdout);
    int T;
    scanf("%d",&T);
    while(T--)
    {
        ll a,b,c;
        scanf("%I64d%I64d%I64d",&a,&b,&c);
        ll x,y;
        ll p=exgcd(a,x,b,y);
        if(c%p) printf("No\n");
        else {
            ll q=c/p,m1=b/p,m2=a/p;
            ll xx=(x*q%m1+m1)%m1;
            ll yy=(y*q%m2+m2)%m2;
            printf("%I64d %I64d %I64d %I64d\n",xx,(c-xx*a)/b,(c-yy*b)/a,yy);
        }
    }
}
/*
3
4 6 3
3 4 7
-2 3 6
*/

 

posted @ 2019-03-03 15:59  yyys  阅读(230)  评论(0编辑  收藏  举报