欧几里得+扩展欧几里得

版权声明:本文为博主原创文章,转载请注明出处( • ̀ω•́ )✧ https://blog.csdn.net/wangws_sb/article/details/82968431

欧几里得算法

欧几里德算法又称辗转相除法,用于计算两个正整数a,b的最大公约数(gcd)。

其计算原理依赖于下面的定理:

定理:gcd(a,b) = gcd(b,a mod b) (a>b 且a mod b 不为0)

证明:a可以表示成a = kb + r,则r = a mod b

假设d是a,b的一个公约数,则有

d|a,d|b,而r = a - kb,因此d|r

因此d也是(b,a mod b)的公约数

因此(a,b)和(b,a mod b)的公约数是一样的,其最大公约数也必然相等,得证

或:证明:

第一步:令c=gcd(a,b),则设a=mc,b=nc

第二步:根据前提可知r =a-kb=mc-knc=(m-kn)c

第三步:根据第二步结果可知c也是r的因数

第四步:可以断定m-kn与n互素【否则,可设m-kn=xd,n=yd,(d>1),则m=kn+xd=kyd+xd=(ky+x)d,则a=mc=(ky+x)dc,b=nc=ycd,故a与b最大公约数≥cd,而非c,与前面结论矛盾】

从而可知gcd(b,r)=c,继而gcd(a,b)=gcd(b,r),得证。

欧几里得(GCD)de递归写法:

int gcd(int a,int b)
{
    if(b == 0) 
        return a;
    else
        return gcd(b,a%b);
}
//更简单写法
int gcd(int a,int b)
{
    return b?gcd(b,a%b):a;//(或return b==0?a:gcd(b,a%b);)
}

由最大公约数又可得最小公倍数(LCM)

lcm(a,b) = (a*b)/gcd(a,b); 代码如下:

int lcm(int a,int b)
{
    return a*b/gcd(a,b);
}
//为了防止a*b超long long 溢出,所以建议下面这种写法

long long(long long a,long long b)
{
    return a/gcd(a,b)*b;
}

gcd  lcm  两个公式的推广:

gcd(k*a,k*b) = k*gcd(a,b);

lcm(k*a,k*b) = k*lcm(a,b);

//这两个公式利用gcd(a/c,b/c) = 1   (注:c = gcd(a,b))可以推理证明

扩展欧几里得算法

内容:若gcd(a,b) = d,那么一定存在x,y使得 ax+by = d,这是一个不定方程,一定有多组解,但是只要找到一组特解x0,y0,就能得到不定方程的通解:

x=x0+b/gcd*k; 

y=y0+a/gcd*k;(k为整数)

扩展欧几里得算法就是在求a,b的最大公约数的同时顺带着把ax+by=d的通解求出来的过程,代码与欧几里得差不多,也是采用的递归写法 :  tx=y;   ty=x-(a/b)*y(两个相邻状态之间的关系,证明过程不会= =);

//扩展欧几里得
ll exgcd(ll a,ll b,ll &x,ll &y){
    //注意x和y必须是引用
    if(!b){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;
}

扩展欧几里得可以判断不定方程ax+by=c是否有整数解,好像欧几里得也是可以判断的-_-||,d=gcd(a,b);如果c可以整除d,那么不定方程ax+by=c有整数解,否则没有。

ax+by=c;
d=gcd(a,b);
if(d%c==0)//有整数解
{
    x=c/d*x0+b/d*k;
    y=c/d*y0-a/d*k;//k为整数
}
else
{
    无整数解
}
因为 ax0+by0=gcd 
所以 a(x0+b/gcd*t)+b(y0-a/gcd*t)=gcd 
所以 x=x0+b/gcd*t, y=y0-a/gcd*t (t为循环变量)

扩展欧几里得还能求逆元(*^▽^*)

使用条件: a,b为正整数,而且gcd(a,b) = 1

证明: 因为a,b 互质,所以一定有 ax+by = 1

两边同时对b 取余

ax%b + by %b = 1%b   ------->   ax%b = 1%b

即 ax ≡ 1 (mod b)

扩展欧几里得中x 就是a关于b的逆元

同理y 就是 b 关于 a的逆元

所以使用完欧几里得算法,我们判断返回值d 是否为1,为1说明 gcd(a,b) = 1

即求得的x就是 a关于b的逆元

void inv(ll a,ll b)
{
    ll x,y;
    if(exgcd(a,b,x,y) == 1)
        cout<<"inv(a):"<<x<<endl;
    else
        cout<<"不存在逆元"<<endl;
}

UPC 9511 Utawarerumono欧几里得判断不定方程是否有整数解,本来以为需要扩展欧几里得,后来发现欧几里得加暴力就可以,不过要判断a,b,c取值的多种情况

AC代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
//ll exgcd(ll a,ll b,ll &x,ll &y){
//    //注意x和y必须是引用
//    if(!b){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;//d是a和b的gcd,顺便求出来
//}
ll gcd(ll a,ll b)
{
    return b?gcd(b,a%b):a;
}
int main()
{
    ll t,ans;
    ll a,b,c,p1,p2,q1,q2;
    scanf("%lld%lld%lld%lld%lld%lld%lld",&a,&b,&c,&p1,&p2,&q1,&q2);
//    ll x,y;
    ll d=gcd(a,b);
    if(a==0&&b==0&&c==0)
    {
        printf("0\n");
        return 0;
    }
    if(a==0&&b==0&&c!=0)
    {
        printf("Kuon\n");
        return 0;
    }
    if(a!=0&&b==0)
    {
        if(c%a!=0)
        {
            printf("Kuon\n");
        }
        else
        {
            t=c/a;
            ans=p2*t*t+p1*t;
            printf("%lld\n",ans);
        }
        return 0;
    }
    if(a==0&&b!=0)
    {
        if(c%b!=0)
        {
            printf("Kuon\n");
        }
        else
        {
            t=c/b;
            ans=q2*t*t+q1*t;
            printf("%lld\n",ans);
        }
        return 0;
    }
    if(c%d==0)
    {
        ll minn=1e18;
        for(ll i=-1e6;i<=1e6;i++)
        {
            if((c-a*i)%b==0)
            {
                ll t=(c-a*i)/b;
                ll ans=p2*i*i+p1*i+q2*t*t+q1*t;
                minn=min(minn,ans);
            }
        }
        printf("%lld\n",minn);
    }
    else
        printf("Kuon\n");
    return 0;
}

HDU 2669 扩展欧几里得裸题

这道题只需要注意两点:gcd(a,b)=1(这个好办,求gcd就行了);还有就是x>=0,这就需要用到扩展欧几里得的通解公式了,如果x<0,可以把x一直加b/gcd(a,b),y一直减a/gcd(a,b),直到x>=0为止。

AC代码:

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <queue>
#include <stack>
#include <vector>
#include <map>
#include <set>
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;
    }
    ll d=exgcd(b,a%b,x,y);
    ll t=x;
    x=y;
    y=t-(a/b)*y;
    return d;
}
int main()
{
    ll a,b,x,y;
    while(cin>>a>>b)
    {
        ll d=exgcd(a,b,x,y);
        if(d==1)
        {
            while(x<0)
            {
                x+=b;
                y-=a;
            }
            cout<<x<<" "<<y<<endl;
        }
        else
            cout<<"sorry"<<endl;
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/wangws_sb/article/details/82968431