1.辗转相除法
虽然很久以前就知道这个方法了,但是一直都不明白原理【汗】
我们假设
G
C
D
(
x
,
y
)
GCD(x,y)
GCD(x,y)为x,y的最大公因数,那么有这样的一个结论:
x>=y时:
G
C
D
(
x
,
y
)
=
G
C
D
(
x
m
o
d
  
y
,
y
)
GCD(x,y)=GCD(x \mod y,y)
GCD(x,y)=GCD(xmody,y)(如果x比y小则x mod y还是等于x,不会有影响)
证明如下:
令x,y的最大公因数为n,则
x
∣
n
,
y
∣
n
x|n,y|n
x∣n,y∣n,
x
m
o
d
  
y
x \mod y
xmody 等同于
x
−
y
∗
k
x-y*k
x−y∗k, 因为
x
∣
n
,
y
∣
n
x|n,y|n
x∣n,y∣n所以
x
m
o
d
  
y
x \mod y
xmody也被n整除,则此时x,y的公因数也是
x
m
o
d
  
y
x \mod y
xmody,y的公因数,因为n时x,y的最大公因数,所以n也是
x
m
o
d
  
y
x \mod y
xmody,y的最大公因数。
有了如上这个结论我们就可以愉快地递推了。
但是为什么是“辗转"相除呢,因为这样就能够避免我上面写到的x比y小的问题。我们每次交换x,y的位置,就能让它不停地除下去。
代码如下:
int gcd(int x,int y){
if (y==0) return x; else return gcd(y,x%y);
}
扩展欧几里德算法(exgcd)
辗转相除法又被称为欧几里德算法,那么扩展欧几里德算法就是在欧几里德算法上稍加改动得到的。
exgcd用于解决形如
a
x
+
b
y
=
G
C
D
(
a
,
b
)
ax+by=GCD(a,b)
ax+by=GCD(a,b)这样的不定方程求特殊根的问题(求出一个特殊根
x
0
,
y
0
x0,y0
x0,y0就可以用公式
x
=
x
0
+
b
G
C
D
(
a
,
b
)
∗
t
,
y
=
y
0
−
a
G
C
D
(
a
,
b
)
∗
t
x=x0+\frac b {GCD(a,b)}*t,y=y0- \frac a {GCD(a,b)}*t
x=x0+GCD(a,b)b∗t,y=y0−GCD(a,b)a∗t来求出所有的根)
其实辗转相除法与不定方程有很深的关系:
辗转相除最后的答案必定是
a
=
G
C
D
(
a
,
b
)
,
b
=
0
a=GCD(a,b),b=0
a=GCD(a,b),b=0.
带入方程
a
x
+
b
y
=
G
C
D
(
a
,
b
)
ax+by=GCD(a,b)
ax+by=GCD(a,b),可求得x=1,y=0。
那么可以表示为:
a
∗
1
+
b
∗
0
=
G
C
D
(
a
,
b
)
a*1+b*0=GCD(a,b)
a∗1+b∗0=GCD(a,b)
对于某一个
x
1
,
y
1
x1,y1
x1,y1,满足:
a
x
1
+
b
y
1
=
G
C
D
(
a
,
b
)
ax1+by1=GCD(a,b)
ax1+by1=GCD(a,b)
那么必定存在另一组x2,y2满足:
b
∗
x
2
+
(
a
m
o
d
  
b
)
y
2
=
G
C
D
(
b
,
a
m
o
d
  
b
)
b*x2+(a \mod b)y2=GCD(b,a\mod b)
b∗x2+(amodb)y2=GCD(b,amodb)(这一项即为辗转相除法求得的下一项)
由上面的结论:
G
C
D
(
x
,
y
)
=
G
C
D
(
x
m
o
d
  
y
,
y
)
GCD(x,y)=GCD(x\mod y,y)
GCD(x,y)=GCD(xmody,y)
可得:
a
∗
x
1
+
b
∗
y
1
=
b
∗
x
2
+
a
m
o
d
  
b
y
2
a*x1+b*y1=b*x2+a \mod by2
a∗x1+b∗y1=b∗x2+amodby2
这个等式也可以写成:
a
∗
x
1
+
b
∗
y
1
=
b
∗
x
2
+
(
a
−
a
b
∗
b
)
∗
y
2
a*x1+b*y1=b*x2+(a-\frac a b*b)*y2
a∗x1+b∗y1=b∗x2+(a−ba∗b)∗y2
变形:
a
∗
x
1
+
b
∗
y
1
=
a
∗
y
2
+
b
∗
x
2
−
a
b
∗
b
∗
y
2
=
a
∗
y
2
+
b
∗
(
x
2
−
a
b
∗
y
2
)
a*x1+b*y1=a*y2+b*x2-\frac a b*b*y2=a*y2+b*(x2-\frac {a} {b}*y2)
a∗x1+b∗y1=a∗y2+b∗x2−ba∗b∗y2=a∗y2+b∗(x2−ba∗y2)
那么就可以发现:
x
1
=
y
2
,
y
1
=
x
2
−
a
b
∗
y
x1=y2,y1=x2-\frac a b*y
x1=y2,y1=x2−ba∗y。
又因为我们确定了边界(1,0),所以可以递推解决。
代码如下:
void exgcd(int a,int b,int &x,int &y){
if (b==0) {x=1,y=0; return;}
exgcd(b,a%b,x,y);
int t=x; x=y,y=t-a/b*y;
}//x,y就是所求的特殊解
扩展欧几里得算法求线性模方程的最小正整数解
我们先考虑一个线性方程,
a
x
≡
b
(
m
o
d
c
)
ax≡b (mod c)
ax≡b(modc)。
我们要求这个方程的一个最小正整数解。
那么
b
b
b一定能够表示成
c
k
+
a
x
ck+ax
ck+ax的形式,因为
a
x
ax
ax和
b
b
b模
c
c
c同余。
那么就可以写成一个不定方程:
a
x
+
b
y
=
c
ax + by = c
ax+by=c
这就可以用不定方程求解。
首先如果gcd(a,b)不是c的倍数,那么肯定无解。
否则,我们我们求一下exgcd(a,b)。
考虑通解的一个方程:
设x0,y0为一组特殊解,可知通解为:
x
=
c
g
c
d
(
a
,
b
)
∗
x
0
+
b
g
c
d
(
a
,
b
)
∗
t
x=\frac c {gcd(a,b)}∗x0+\frac b {gcd(a,b)}∗t
x=gcd(a,b)c∗x0+gcd(a,b)b∗t
y
=
c
g
c
d
(
a
,
b
)
∗
y
0
–
a
g
c
d
(
a
,
b
)
∗
t
,
t
∈
Z
y=\frac c {gcd(a,b)}∗y0–\frac a {gcd(a,b)}∗t,t∈Z
y=gcd(a,b)c∗y0–gcd(a,b)a∗t,t∈Z
那么根据这一式子,我们可以推出最小的一组正整数解:
(
x
0
∗
c
g
c
d
(
a
,
b
)
m
o
d
  
b
+
b
)
m
o
d
  
b
(x0* \frac c {gcd(a,b)}\mod b+b) \mod b
(x0∗gcd(a,b)cmodb+b)modb
例题:BZOJ1477 青蛙的约会:传送门
代码如下:
#include<cstdio>
#include<string>
#define ll long long
using namespace std;
ll x,y,m,n,L,a,b;
ll exgcd(ll x,ll y,ll &x0,ll &y0){
if (y==0) {x0=1,y0=0; return x;}
ll ret=exgcd(y,x%y,x0,y0);
ll t=x0; x0=y0,y0=t-x/y*y0;
return ret;
}
ll solve(ll a,ll b,ll c){
ll x0,y0,g=exgcd(a,b,x0,y0);
if (c%g) return -1;
x0=x0*c/g; y0=y0*c/g;
x0=(x0%b+b)%b;
return x0;
}
int main(){
scanf("%lld %lld %lld %lld %lld",&x,&y,&m,&n,&L);
if (solve(n-m,L,x-y)==-1) printf("Impossible\n");
else printf("%lld\n",solve(n-m,L,x-y));
return 0;
}
update 2018.7.20 更新了latex公式。