【luoguP5656】二元一次不定方程(gcd,exgcd,裴蜀定理,不定方程初步)

我是题目链接qwq

前言(概述)

这个题目对于像我这种的数论新手极其不友好,首先这个题面就很不友好,所以我们先来分解一下题面,看看他想让我们干什么qwq。

首先,我们需要判断给定的不定方程有无整数解,若无,输出-1,若有,则分类讨论:

  • 有正整数解,则先输出解的数量,再按顺序输出x的最小正整数解,y的最小正整数解,x的最大正整数解,y的最大正整数解。
  • 无正整数解,则输出x的最小正整数解,y的最小正整数解。

现在我们知道了要求什么了,那么怎么求呢qwq,让我们一步一步来。

背景芝士

裴蜀定理

要解决这个问题我们首先要明白一个叫做裴蜀定理的东西(左转),即:

对于任何整数$a$,$b$和它们的最大公约数$d$,关于未知数$x$和$y$的线性不定方程(称为裴蜀等式):

若$a$,$b$是整数,且$gcd(a,b)=d$,那么对于任意的整数$x$,$y$,$ax+by$都一定是$d$的倍数。

特别地,一定存在整数$x$,$y$使$ax+by=d$成立。——百度百科。

简单来说,就是不定方程$ax+by=c$,其中$xinmathbb{N}^+$,$yinmathbb{N}^+$,有解的充要条件是$gcd(a,b)|c$

那么下面我们来证明一下:

首先,设$s=gcd(a,b)$,

显然$s|a$,$s|b$,

$ecause x,yinmathbb{N}^+$,

$ herefore s|ax,s|by,s|ax+by$,

令$p$为$ax+by$的最小正整数值,

$q=lfloor frac{a}{p} floor$,

$r=a mod p=a-qp=a-q(ax+by)=a(1-qx)+bqy$,

可知,$r$也是$a$,$b$的线性组合(简单来说就是$r$也属于$ax+by$的一种形式)。

$ecause p$为$ax+by$的最小正整数值,$0 le r < p$,

故$r=0$,即$p|a$。

同理,可得$p|b$,

$ecause$所有的$ax+by$都是$s$的倍数。

$ herefore p=d$。

$mathrm{Q.E.D.}$

欧几里得定理求gcd

P.S.如果会求gcd可以跳过qwq

那么如何求两个数的gcd呢,一般有三种方法:

  1. 欧几里得(辗转相除)法
  2. 九章算术·更相减损法
  3. stein算法
  4. 暴力

我们这里只讲最常用的欧几里得算法

即:$gcd(a,b)=gcd(b,a mod b)$

证明:

设$a=kb+r$

则有,$r=a mod b$

设:$d$为$a$,$b$的一个任意的公约数,

则有$a equiv b equiv 0 pmod{d}$

则,由同余的性质可得:

$a-kb equiv r equiv 0 pmod{d}$

$ herefore d$是$b$和$a mod b$的公约数。

$ecause forall d|gcd(a,b)$都满足此性质

$ herefore gcd(a,b)=gcd(b,a mod b)$

$mathrm{Q.E.D.}$

然后我们就可以得到一个很简单的函数:

int gcd(int a,int b)
{
    return b?gcd(b,a%b):a;
}

时间复杂度$O(logn)$,大部分情况下已经够用了,如果你想要更快,更强,那么你可以尝试了解一下stein算法,他是对欧几里得算法的一种优化,比这个要快。

exgcd解线性同余方程

有欧几里得,就一定会有拓展欧几里得。——鲁迅

咳咳,下面我们来说拓展欧几里得算法qwq。

欧几里得算法提供了一种快速计算最大公约数的方法。而扩展欧几里得算法不仅能够求出其最大公约数。而且能够求出a,b和其最大公约数构成的不定方程ax+by=c的两个整数x,y(这里x和y不一定为正数)。

那么,拓欧(是拓展欧几里得,不是那个毒瘤的拓展欧拉定理,究竟是怎么运算的呢,让我们来康康

首先我们先求我们先求对于$ax+by=1$且$a perp b$的一组整数解$(x,y)$(为了避免我口胡,下面说的所有解都是整数解qwq

(by the way,$ax+by=c$与$ax equiv c pmod{b}$等价,且由裴蜀定理可知$ax+by=1$有一组整数解为$a perp b$的充要条件。

那么下面让我们用朴素欧几里得算法来推一推式子,来求出上式的一组整数解:

$ax+by=1$

$ecause a perp b$

$ herefore ax+by=gcd(a,b)$

$ecause gcd(a,b)=gcd(b,a mod b)$

$ herefore ax+by=gcd(b,a mod b) ightarrow bx+(a mod b)y$

$ecause a mod b=a- lfloor frac{a}{b} floor b$

$ herefore ax+by=bx+(a- lfloor frac{a}{b} floor b)y$

提取公因数,得:

$ax+by=ay+b(x- lfloor frac{a}{b} floor y)$

此时,我们可以发现,不定方程的一组解为$egin{cases} x=y\y=(x- lfloor frac{a}{b} floor y) end{cases}$

于是乎我们就求出了这个方程的一组解,那么这个式子有什么用呢,我们要解的方程$ax+by=c$与它有什么关系呢?

我们发现,对于任意的$ax+by=c$满足$gcd(a,b)|c$都可以转化为$ax+by=1$的形式,然后我们就可以使用exgcd进行求解了(由裴蜀定理可知,若$gcd(a,b) mid c$则该方程无解。

下面让我们对这个更一般的方程$ax+by=c$转化为$ax+by=1$的形式:

令$a'= frac{a}{gcd(a,b)} quad b'= frac{b}{gcd(a,b)} quad c'= frac{c}{gcd(a,b)}$

$ herefore a'x+b'y=c'$

$ herefore a' frac{x}{c'}+b' frac{y}{c'}=1$

令$x'=frac{x}{c'} quad y'=frac{y}{c'}$

$ herefore a'x'+b'y'=1$

然后我们使用刚才的方法跑一遍exgcd即可得到此方程的一组解$(x',y')$

则原方程的解$egin{cases} x=x'c'\y=y'c' end{cases}$

此时,我们就可以通过递归求出这组解

参考代码如下:

void exgcd(int a,int b,int &x,int &y)
{
    if(!b)
    x=1,y=0;
    else
    {
        exgcd(b,a%b,x,y);
        int t=x;
        x=y;
        y=t-a/b*y;
    }
}
/*可以将函数定为int类型,在else处定义变量gcd=exgcd(b,a%b,x,y),在else的最后return gcd;在if处最后return a,此时的gcd或a即为gcd(a,b)。
函数参数中a,b,x,y即为方程中的a,b,x,y,初始时,x,y不赋值,因为是传址调用,函数递归结束后的x,y即为原方程的解。*/

解系扩展 

通过上述方法我们只求出了方程的一组解,而且这个解是随机的一组解,它可能并不是我们想要的解(如本题,很多题让我们求出$x or y$的最小正整数解。

这时我们就需要讲单个解$(x,y)$扩展成一个解系(解集。

构造一个$p,q$,即我们想要让$x$变为$x+p$,让$y$变成$y-q$

$egin{cases} a(x_0+p)+b(y_0-q)=c\ax_0+by_0=c end{cases}$

解方程组,可知,$p=frac{bq}{a}$

同时,因为我们要保证$a,b,x,y$都为整数,

所以$a|bq,b|bq$

即最小的$bq=lcm(a,b)=frac{ab}{gcd(a,b)}$

所以最小的$p=frac{b}{gcd(a,b)},q=frac{a}{gcd(a,b)}$

返回刚才的等式$a(x_0+p)+b(y_0-q)=c$,我们就得到了这里最小的$p$和$q$

这时我们只需用一个整数系数$t$乘上$p$和$q$就可以得到整个解系了。

到这里,我们就有了可以解出这个题的基础芝士,下面,我们来对这个问题逐步求解。

逐步求解

有无整数解

根据裴蜀定理我们可知,第一个问题,有无正整数解,即可转化为判断$gcd(a,b)|c   or   gcd(a,b) mid c$。

当$gcd(a,b)|c$时,有解;

反之,无解。

第一个问题解决。

x或y的最小值

刚才我们对方程进行了解析拓展,得到了所有的$(x,y)$

我们回到刚才的等式,变换一下变量,

那么我们就有了一组通解,

注意0不为正整数,所以需要特判(这个会卡掉几个点。。。)

若$x or y=0$则将其加上$b or a$即可

第二个问题解决。

x或y的最大值

由$a'x+b'y=c'$可知,$x$与$y$的数量大小成反比

$ herefore$ egin{cases} a'x_{min}+b'y_{max}=c'\a'x_{max}+b'y_{min}=c' end{cases} egin{cases} x_{max}=frac{c'-b'y_{min}}{a'}\y_{max}=frac{c'-a'x_{min}}{b'} end{cases}

第三个问题解决。

判断有无正整数解

有了上面的$x_{max} and y_{max}$的值,我们就可以判断,若最大值还不为正整数,则无正整数解

第四个问题解决。

正整数解的数量

由$x=x_{min}+b'k$可知,$x_{min}$到$x_{max}$之间每有$b'$个数,就有一个解。

所以正整数解的数量即为$lfloor frac{x_{max}-x_{min}}{b'}+1 floor$(+1是因为边界问题,向下取整会舍去,所以会少算一个边界

y同理,如果你喜欢y你也可以用y求qwq。

第五个问题解决,然后这个毒瘤的题目就结束惹qwq。

参考代码

#include<bits/stdc++.h>

using namespace std;

typedef long long ll;

inline long long read()
{
    ll x=0;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x;
}

inline void print(long long x)
{
    if(x>9) 
    print(x/10);
    putchar(x%10+'0');
}

ll mul(ll a,ll b,ll p)
{
    return a*b%p;
}

ll Gcd(ll a,ll b)//stein算法求gcd
{
    ll c=0;
    while(1)
    {
        if(!a)
        return b<<c;
        else if(!b)
        return a<<c;
        if(!(a&1)&&!(b&1))
        a>>=1,b>>=1,++c;
        else if(!(a&1)&&(b&1))
        a>>=1;
        else if((a&1)&&!(b&1))
        b>>=1;
        else if((a&1)&&(b&1))
        {
            ll x=min(a,b);
            a=abs(a-b);
            b=x;
        }
    }
}

void exgcd(ll a,ll b,ll &x,ll &y)
{
    if(!b)
    {
        x=1ll,y=0ll;
    }
    else
    {
        exgcd(b,a%b,x,y);
        ll t=x;
        x=y;
        y=t-a/b*y;
    }
}

ll t;

int main()
{
    t=read();
    while(t--)
    {
        ll a=read(),b=read(),c=read(),x,y;
        ll gcd=Gcd(a,b);
        if(c%gcd)
        {
            puts("-1");
            continue;
        }
        else
        {
            a/=gcd,b/=gcd,c/=gcd;
            exgcd(a,b,x,y);
            x*=c,y*=c;
            ll xmin=(x%b+b)%b;
            if(!xmin)
            xmin+=b;
            ll ymin=(y%a+a)%a;
            if(!ymin)
            ymin+=a;
            ll xmax=(c-b*ymin)/a;
            ll ymax=(c-a*xmin)/b;
            if(xmax<=0||ymax<=0)
            {
                print(xmin);
                printf(" ");
                print(ymin);
                puts("");//由于他这道毒瘤题卡常过于严重,所以这个鬼畜的输出请见谅qwq
            }
            else
            {
                ll sum=(xmax-xmin)/b+1;
                print(sum);
                printf(" ");
                print(xmin);
                printf(" ");
                print(ymin);
                printf(" ");
                print(xmax);
                printf(" ");
                print(ymax);
                puts("");
            }
        }
    }
}
对不起啊,因为我是一个活在二次元的人,生为野犬,喻世,勿争了吧。
原文地址:https://www.cnblogs.com/YLCHANGE/p/13523248.html