前言(概述)
这个题目对于像我这种的数论新手极其不友好,首先这个题面就很不友好,所以我们先来分解一下题面,看看他想让我们干什么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呢,一般有三种方法:
- 欧几里得(辗转相除)法
- 九章算术·更相减损法
- stein算法
- 暴力
我们这里只讲最常用的欧几里得算法
即:$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(""); } } } }