【扩展欧几里得算法】(一)解同余方程 学习笔记
用来干嘛的?
给定正整数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(a−⌊ba⌋b)=day+bx−⌊ba⌋by=day+b(x−⌊ba⌋y)=dx′=y,y′=x−⌊ba⌋y
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=dcy0−kda
由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=y0−kda
最特殊的
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=y0−ka
上面三个要区分开来
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) ax≡b(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
ax≡1(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=y0−ka
并且题目要求解最小正整数,所以
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
(x0∗b/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(m−n)+y0L=d的一组解
然后乘上比例
b
−
a
d
\frac{b-a}d
db−a得到原方程的一组解
b
−
a
d
x
0
\frac {b-a}d x_0
db−ax0
x
=
b
−
a
d
x
0
+
k
L
d
x=\frac {b-a} dx_0+k \frac L d
x=db−ax0+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=(db−ax0%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
x或y的最大最小值,并且他们都符合通解形式,所以求正整数解的个数实际上就是看通解里的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)bxmax−xmin+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的特解
x1,y1为ax+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=0那么x=p
输出int范围内的解
x
=
x
1
%
b
x=x1\%b
x=x1%b