同余1:欧几里得算法及其拓展学习笔记
前言:搞定了类欧几里得后,我决定把欧几里得算法给复习一下,顺带复习整个同余相关问题(
欧几里得算法
欧几里得算法没什么可说的,就是求最大公约数,不过有多种方式,这里不再讨论暴力的求法了。
辗转相除法
接下来,直接就到辗转相除法了,辗转相除法用到下面的结论,当其中一个数辗转到零时另一个数就是答案。
结论:
G
C
D
(
X
,
Y
)
=
G
C
D
(
Y
,
X
%
Y
)
GCD(X,Y) =GCD(Y,X \% Y)
GCD(X,Y)=GCD(Y,X%Y);
证明:设对任意两个不为一的正整数
x
x
x和
y
y
y有最大公因数为
d
d
d,则
x
x
x可表示为
t
1
∗
d
t_1*d
t1∗d,
y
y
y可表示为
t
2
∗
d
t_2 * d
t2∗d,根据最大公因数的定义可知,
t
1
t_1
t1和
t
2
t_2
t2互质。这里,不妨设
x
>
y
x > y
x>y,则
t
1
>
t
2
t_1 > t_2
t1>t2,根据基本常识
a
%
b
=
a
−
b
∗
⌊
a
b
⌋
a \% b = a - b * \lfloor {a \over b} \rfloor
a%b=a−b∗⌊ba⌋,因此
X
%
Y
X \% Y
X%Y可表示成
t
1
∗
d
−
t
2
∗
d
∗
⌊
t
1
∗
d
t
2
∗
d
⌋
t_1 * d - t_2 * d * \lfloor {t_1 * d \over t_2 * d} \rfloor
t1∗d−t2∗d∗⌊t2∗dt1∗d⌋,约去
d
d
d,得
t
1
∗
d
−
t
2
∗
d
∗
⌊
t
1
t
2
⌋
=
(
t
1
−
t
2
∗
⌊
t
1
t
2
⌋
)
∗
d
t_1 * d - t_2 * d * \lfloor {t_1\over t_2 } \rfloor = (t_1 - t_2 * \lfloor {t_1 \over t_2} \rfloor)*d
t1∗d−t2∗d∗⌊t2t1⌋=(t1−t2∗⌊t2t1⌋)∗d,不难看出括号内的数为整数,则上述结论成立。
通过这个方法,不难看出
G
C
D
(
X
,
Y
)
=
G
C
D
(
Y
,
X
−
Y
)
GCD(X,Y) =GCD(Y,X - Y)
GCD(X,Y)=GCD(Y,X−Y)也成立。
inline int gcd( int x , int y ) {
return y == 0 ? x : gcd( y , x % y );/*上述讨论建立在x > y的基础上,实际上
这里若x < y,gcd会自动帮我们交换两个数*/
}
时间复杂度为 O ( l o g ( X ) ) O(log(X)) O(log(X))
二进制算法
我以为这样就最优了,实际上我还看到一种“二进制算法”,是在《信息学竞赛之数学一本通》(东南大学出版社)里面看到的,这里也稍微提一下,具体思想就是通过不断去掉因子2来降低常数。过程如下:
1.若
x
x
x,
y
y
y均为偶数,则
G
C
D
(
x
,
y
)
=
2
∗
G
C
D
(
x
,
y
)
GCD(x,y) =2*GCD(x,y)
GCD(x,y)=2∗GCD(x,y);
2.若
x
x
x,
y
y
y均为奇数,则
G
C
D
(
x
,
y
)
=
G
C
D
(
x
−
y
,
y
)
GCD(x,y) =GCD(x - y,y)
GCD(x,y)=GCD(x−y,y);
3.若
x
x
x,
y
y
y一奇一偶,不妨设
x
x
x为偶数(否则将两数置换),则
G
C
D
(
x
,
y
)
=
G
C
D
(
x
2
,
y
)
GCD(x,y) =GCD({ x \over 2},y)
GCD(x,y)=GCD(2x,y);
4.当其中一个数为零时返回另一个数。
这个做法的正确性显然,不难得出以下代码:
inline int gcd( int x , int y ) {
if ( !y ) {
return x;
}
if ( x < y ) {
x ^= y ^= x ^= y;//整数的快速交换
}
if ( x & 1 && y & 1 ) {
return gcd( y , x - y );
} else if ( x & 1 ) {
return gcd( x , y / 2 );
} else if ( y & 1 ) {
return gcd( x / 2 , y );
} else {
return 2 * gcd( x / 2 , y / 2 );
}
}
最小公倍数LCM可利用GCD求出,就是
L
C
M
(
a
,
b
)
=
a
∗
b
G
C
D
(
a
,
b
)
LCM(a,b)={ a * b \over GCD(a,b)}
LCM(a,b)=GCD(a,b)a∗b,证明自己想吧。
在数据非常大以致不得不用高精度的时候,二进制算法的优越性更加明显。
拓展欧几里得算法
拓展欧几里得算法
拓展欧几里得算法可用来求解以下问题,对于已知数(a,b),找出另一组数(p,q),使得满足
a
∗
p
+
b
∗
q
=
G
C
D
(
a
,
b
)
a*p+b*q=GCD(a,b)
a∗p+b∗q=GCD(a,b),没错,这就是裴蜀定理的内容,我们的目标是证明存在性,如果存在就找到一个合适的算法。
接下来,让我们利用
G
C
D
(
X
,
Y
)
=
G
C
D
(
Y
,
X
%
Y
)
GCD(X,Y) =GCD(Y,X \% Y)
GCD(X,Y)=GCD(Y,X%Y)搞些事情:
已知
a
∗
p
+
b
∗
q
=
G
C
D
(
a
,
b
)
a*p+b*q=GCD(a,b)
a∗p+b∗q=GCD(a,b),通过
G
C
D
(
a
,
b
)
=
G
C
D
(
b
,
a
%
b
)
GCD(a,b) =GCD(b,a \% b)
GCD(a,b)=GCD(b,a%b),
得出
b
∗
p
′
+
a
%
b
∗
q
′
=
a
∗
p
+
b
∗
q
b*p'+a \% b * q' = a*p+b*q
b∗p′+a%b∗q′=a∗p+b∗q
推出
b
∗
p
′
+
(
a
−
b
∗
⌊
a
b
⌋
)
∗
q
′
=
a
∗
p
+
b
∗
q
b*p'+(a - b * \lfloor {a \over b} \rfloor) * q' =a*p+b*q
b∗p′+(a−b∗⌊ba⌋)∗q′=a∗p+b∗q
最终得出:
a
∗
q
′
+
b
∗
(
p
′
−
q
′
∗
⌊
a
b
⌋
)
=
a
∗
p
+
b
∗
q
a *q'+b*(p' - q'*\lfloor {a \over b} \rfloor) = a*p+b*q
a∗q′+b∗(p′−q′∗⌊ba⌋)=a∗p+b∗q
这样就将p,q的变化与a,b的变化挂钩了。
当b = 0时,GCD为a,不难看出p= 1,q = 0,再将(p,q)回溯,经过层层递归回去,即可得到原先(p,q)的值,至此存在性的证明与算法都出来了。
inline int exgcd( int a, int b , int & x , int & y ) {//返回x,y的值即为(p,q)
if ( !b ) {
x = 1;
y = 0;
return a;
}
int ans = exgcd( b , a % b , x , y );
int tem = x;
x = y;
y = tem - a / b * y;
return ans;
}
拓展欧几里得算法的应用(解线性同余方程)
拓展欧几里得算法主要用来线性同余方程。
对于方程
a
∗
x
+
b
∗
y
=
c
a*x+b*y=c
a∗x+b∗y=c, 该方程等价于
a
∗
x
≡
c
(
m
o
d
b
)
a * x\equiv c\pmod b
a∗x≡c(modb),而该方程有整数解的充要条件为
c
%
G
C
D
(
a
,
b
)
=
0
c \% GCD(a,b)=0
c%GCD(a,b)=0。
对于求解方程,我们先求出
(
x
o
,
y
o
)
(x_o,y_o)
(xo,yo)满足
a
∗
x
o
+
b
∗
y
o
=
G
C
D
(
a
,
b
)
a*x_o+b*y_o=GCD(a,b)
a∗xo+b∗yo=GCD(a,b),则方程解可表示为
a
∗
x
o
∗
c
G
C
D
(
a
,
b
)
+
b
∗
y
o
∗
c
G
C
D
(
a
,
b
)
=
G
C
D
(
a
,
b
)
∗
c
G
C
D
(
a
,
b
)
a*x_o * {c \over GCD(a,b)}+b*y_o* {c \over GCD(a,b)}=GCD(a,b)* {c \over GCD(a,b)}
a∗xo∗GCD(a,b)c+b∗yo∗GCD(a,b)c=GCD(a,b)∗GCD(a,b)c,这同时也说明了上述充要条件的合理性。
不过,这样求出的只是一组特解,解的一般形式为
x
=
x
o
∗
c
G
C
D
(
a
,
b
)
+
t
∗
b
G
C
D
(
a
,
b
)
x = x_o * {c \over GCD(a,b)} +t*{b \over GCD(a,b)}
x=xo∗GCD(a,b)c+t∗GCD(a,b)b
y
=
y
o
∗
c
G
C
D
(
a
,
b
)
−
t
∗
a
G
C
D
(
a
,
b
)
y = y_o * {c \over GCD(a,b)} -t*{a \over GCD(a,b)}
y=yo∗GCD(a,b)c−t∗GCD(a,b)a
其中
t
t
t为任意正整数。
不过,在做题时,我们一般要求最小正整数解,求法为:
设
z
=
b
G
C
D
(
a
,
b
)
z = {b \over GCD(a,b)}
z=GCD(a,b)b,
x
=
(
x
%
z
+
z
)
%
z
x = (x \% z + z) \% z
x=(x%z+z)%z,y同理。
当
a
a
a与
b
b
b互质,
c
=
1
c = 1
c=1时,求出的
x
x
x即为
a
a
a模
b
b
b意义下的逆元(不懂逆元的可以假装没看到 )。
好了,总体上来说欧几里得算法就没什么了(线性逆元以后讲吧 ),最重要的还是它的应用。这个算法我以前也早已掌握,但在做题时总是依赖题解,证明自己其实并不熟悉它的原理,所谓数论,不是拿来出题的,而是用来快速算出某一步式子的解的,还是要有灵活的思维啊。所以,想要牢固掌握一个知识点,还得多刷题。
板子
同余方程(太裸了 )https://www.luogu.org/problem/P1082
我都不想讲了。
#include <iostream>
#include <cstdio>
#define ll long long
using namespace std;
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 , x , y );
ll t = x;
x = y;
y = t - ( a / b ) * y;
return d;
}
int main () {
ll a , b , c , x , y;
cin >> a >> b;
c = 1;//其实是多余的
ll d = exgcd( a , b , x , y );
x = x * ( c / d );
y = y * ( c / d );
ll z = b / d;
ll k = x;
k = ( x % z + z ) % z;
printf("%lld\n",k);
return 0;
}