【扩展欧几里得算法】(一)解同余方程 学习笔记

【扩展欧几里得算法】(一)解同余方程 学习笔记

用来干嘛的?

给定正整数a,b,最大公约数d。我们想要求解 a x + b y = d ax+by=d ax+by=d的x,y,扩展欧几里得算法可以解决这一问题

如何实现

扩展欧几里得基于欧几里得算法,回忆一下欧几里得算法求GCD的。
( a , b ) = ( b , a m o d    b ) (a,b)=(b,a\mod b) (a,b)=(b,amodb)
可以缩小问题规模,把求 ( a , b ) (a,b) (a,b)最大公约数转化为求 ( b , a m o d    b ) (b,a\mod b) (b,amodb)的最大公约数
相似的
我们可以先求解出
( b , a m o d    b ) = d … … ① (b,a\mod b)=d……① (b,amodb)=d
b x + ( a m o d    b ) y = d … … ② bx+(a\mod b)y=d……② bx+(amodb)y=d
然后我们可以利用②式求解出原问题
a x ′ + b y ′ = d … … ③ ax^{'}+by^{'}=d……③ ax+by=d
x b + y ( a m o d    b ) = d x b + y ( a − ⌊ a b ⌋ b ) = d a y + b x − ⌊ a b ⌋ b y = d a y + b ( x − ⌊ a b ⌋ y ) = d x ′ = y , y ′ = x − ⌊ a b ⌋ y xb+y(a\mod b)=d\\ xb+y(a-\lfloor\frac a b\rfloor b)=d \\{}\\ ay+bx-\lfloor \frac a b\rfloor by=d \\{} \\ ay+b(x-\lfloor \frac a b\rfloor y)=d\\{}\\ x^{'}= y , y^{'}=x-\lfloor \frac a b\rfloor y\\ xb+y(amodb)=dxb+y(abab=day+bxbaby=day+b(xbay)=dx=y,y=xbay
exgcd得到了 a x 0 + b y 0 = d ax_0+by_0=d ax0+by0=d的特解,然后得到原线性同余方程的一个特解 c d x 0 \frac c d x_0 dcx0
线性同余方程的通解的形式
x = c d x 0 + k b d y = c d y 0 − k a d x=\frac cdx_0+k\frac b d\\ {}\\ y=\frac c dy_0-k\frac a d\\ x=dcx0+kdby=dcy0kda
exgcd得到的通解形式为(和上面的区分一下)即 a x + b y = d ax+by=d ax+by=d的解

x = x 0 + k b d y = y 0 − k a d x=x_0+k\frac b d\\{}\\ y=y_0-k\frac a d\\ x=x0+kdby=y0kda
特殊 a x + b y = 1 ax+by=1 ax+by=1
x = x 0 + k b y = y 0 − k a x=x_0+k b\\{}\\ y=y_0-ka\\ x=x0+kby=y0ka
上面三个要区分开来
ax+by=c有解的充要条件是gcd(a,b)|c

代码模板

int exgcd(int a,int b ,int &x,int &y)
{
    if(!b)
    {
        x=1,y=0;//b=0的的时候,ax+by=gcd(a,b)=a,x=1,y=0;
        return a;
    }
    
    int d=exgcd(b,a%b,y,x);//交换参数实现
    y-=a/b*x;
    return d;
}

模板练习题:扩展欧几里得算法

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

int exgcd(int a,int b,int &x,int &y)
{
    if(!b)
    {
        x=1,y=0;
        return a;
    }
    int d=exgcd(b,a%b,y,x);
    y-=a/b*x;
    return d;
}
int main()
{
    int a,b,n;
    cin>>n;
    while(n--)
    {
        cin>>a>>b;
        int x,y;
        exgcd(a,b,x,y);
        cout<<x<<" "<<y<<endl;
    }
    return 0;
}

应用案例

一、扩展欧几里得求线性同余方程

给a,b,c求解 a x ≡ b ( m o d    c ) ax\equiv b(\mod c) axb(modc)等价于 a x + c y = b ax+cy=b ax+cy=b

例题1:同余方程

思路
a x ≡ 1 ( m o d    b ) 等 价 与 a x + b y = 1 ax\equiv 1(\mod b)等价与ax+by=1 ax1(modb)ax+by=1
他们的解满足如下形式
x = x 0 + k b y = y 0 − k a x=x_0+kb\\ y=y_0-ka\\ x=x0+kby=y0ka
并且题目要求解最小正整数,所以 x 0 + k b ! = 0 x_0+kb!=0 x0+kb!=0,那么最小正整数解只需要 x 0 m o d    b x_0 \mod b x0modb即可
代码

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

typedef long long LL;

int exgcd(int a,int b ,int &x,int &y)
{
    if(!b)
    {
        x=1,y=0;
        return a;
    }
    
    int d=exgcd(b,a%b,y,x);
    y-=a/b*x;
    return d;
}
int get_mod(int a,int b)
{
    return (a%b+b)%b;
}
int main()
{
    int a,b;
    cin>>a>>b;
    int x,y;
    exgcd(a,b,x,y);
    cout<<get_mod(x,b)<<endl;
    return 0;
}

例题2:线性同余方程
思路
转化为求解 a x + m y = b ax+my=b ax+my=b
有解的充要条件是gcd(a,m)|b
通解的形式为 x = b d x 0 + m d k x=\frac bdx_0+\frac m dk x=dbx0+dmk
要保证输出解在int范围内可以
相关证明
( x 0 ∗ b / d ) % m (x_0*b/d)\%m (x0b/d)%m
把%m换成%a或者%b不可以
代码

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

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

int main()
{
    int n;
    cin>>n;
    while(n--)
    {
        int a,b,m,x,y;
        cin>>a>>b>>m;
        //即求解ax+my=b
        int d=exgcd(a,m,x,y);
        if(b%d)puts("impossible");
        else cout<<(LL)x*b/d%m<<endl;
    }
    return 0;
}

例题3:青蛙约会

思路根据题意,可以得到
在这里插入图片描述
exgcd得到的 x 0 ( m − n ) + y 0 L = d x_0(m-n)+y_0L=d x0(mn)+y0L=d的一组解
然后乘上比例 b − a d \frac{b-a}d dba得到原方程的一组解 b − a d x 0 \frac {b-a}d x_0 dbax0
x = b − a d x 0 + k L d x=\frac {b-a} dx_0+k \frac L d x=dbax0+kdL
t = L d t=\frac L d t=dL
最小正整数解: x m i n = ( b − a d x 0 % t + t ) % t x_{min}=(\frac {b-a} dx_0\%t+t)\%t xmin=(dbax0%t+t)%t

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

int main()
{
    LL a,b,m,n,l,x,y;
    cin>>a>>b>>m>>n>>l;
    LL d=exgcd(m-n,l,x,y);

    if((b-a)%d==0)
    {
        x=x*(b-a)/d;
        LL t=abs(l/d);
        cout<<(x%t+t)%t;
    }
    else printf("Impossible");
    return 0;
}

例题4:P5656 【模板】二元一次不定方程 (exgcd)
思路
这题貌似挺重要的。
要求解决一下问题
1判断二元一次不定方程有没有解
2如果有解,那么输出正整数解的个数, x m i n , y m i n , x m a x , y m a x x_{min},y_{min},x_{max},y_{max} xmin,ymin,xmax,ymax
首先判断有没有整数解
a x + b y = c ax+by=c ax+by=c有没有整数解就看是否满足 g c d ( a , b ) ∣ c gcd(a,b)|c gcd(a,b)c,满足则有解,不满足则没有解
然后我们可以用扩展欧几里得求出 a x + b y = g c d ( a , b ) ax+by=gcd(a,b) ax+by=gcd(a,b)的一组特解 x 0 , y 0 x_0,y_0 x0,y0
那么我们按比例放大(左右乘上 c g c d ( a , b ) \frac c gcd(a,b) gccd(a,b)回到原方程,可以得到
a x + b y = c ax+by=c ax+by=c的一组特解 x 1 = x 0 c g c d ( a , b ) , y 1 = y 0 c g c d ( a , b ) x_1=\frac{ x_0c} {gcd(a,b)},y_1=\frac {y_0c} {gcd(a,b)} x1=gcd(a,b)x0c,y1=gcd(a,b)y0c
同时我们可以知道其通解的形式
x = x 1 + k b g c d ( a , b ) y = y 1 + k a g c d ( a , b ) x=x1+k\frac b {gcd(a,b)}\\{}\\ y=y1+k \frac a {gcd(a,b)} x=x1+kgcd(a,b)by=y1+kgcd(a,b)a
再观察一下 a x + b y = c ax+by=c ax+by=c我们可以发现x,y实际上就是直线ax+by=c上的整数点。由于a,b,c均为正数,我们可以大致画出这条直线的图像
在这里插入图片描述
我们要研究的就是这段里的正整数解
观察图像我们可以得出这样的性质
x越小y越大,x越大y越小。所以求得 x m i n x_{min} xmin就可以得到 y m a x y_{max} ymax,求得
y m i n y_{min} ymin就可以得到 y m a x y_{max} ymax,对于求x或者y的最小正整数解,我们之前已经知道了。同时我们可以利用两个最大值判有没有正整数解
现在的问题是正整数解的个数怎么求,我们已经得到了 x 或 y x或y xy的最大最小值,并且他们都符合通解形式,所以求正整数解的个数实际上就是看通解里的k从最小到最大变化多少。那么我们就可以得到解的数量为 c n t = x m a x − x m i n b g c d ( a , b ) + 1 cnt=\frac {x_{max}-x_{min}} {\frac b {gcd(a,b)}}+1 cnt=gcd(a,b)bxmaxxmin+1

代码

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

ll read()
{
    ll x=0,f=0;char ch=getchar();
    while(!isdigit(ch))f|=ch=='-',ch=getchar();
    while(isdigit(ch))x=10*x+ch-'0',ch=getchar();
    return f?-x:x;
}
int main()
{
    ll a,b,c,T;
    T=read();
    while(T--)
    {
        ll x,y;
        a=read();b=read();c=read();
        ll d=exgcd(a,b,x,y);
        if(c%d)puts("-1");
        else
        {
            x*=c/d;
            y*=c/d;
            ll p=b/d,q=a/d;
            ll x_min=(x%p+p)%p,y_min=(y%q+q)%q;//求最小
            if(x_min==0)x_min=p;//防止取模后直接输出0了
            if(y_min==0)y_min=q;

            ll y_max=(c-a*x_min)/b,x_max=(c-b*y_min)/a;
            ll cnt=(x_max-x_min)/p+1;
            if(y_max<=0||x_max<=0)printf("%lld %lld\n",x_min,y_min);//没有正整数解
            else printf("%lld %lld %lld %lld %lld\n",cnt,x_min,y_min,x_max,y_max);

        }
    }

    return 0;
}

总结

在这里插入图片描述
输出最小正整数解
x 1 , y 1 为 a x + b y = c 的 特 解 x1,y1为ax+by=c的特解 x1y1ax+by=c
那么他的最小正整数解为
d = g c d ( a , b ) , p = a b s ( b d ) 防 止 出 现 负 数 d=gcd(a,b),p=abs(\frac b d)防止出现负数 d=gcd(a,b),p=abs(db)
x = ( ( x + p ) % p ) % p , 如 果 x = 0 那 么 x = p x=((x+p)\%p)\%p,如果x=0那么x=p x=((x+p)%p)%p,x=0x=p
输出int范围内的解
x = x 1 % b x=x1\%b x=x1%b

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值