超级详细的基础算法和数据结构合集:
https://blog.csdn.net/GD_ONE/article/details/104061907
摘要
本文主要讲解欧几里得算法和扩展欧几里得算法。
欧几里得算法
欧几里得算法就是辗转相除法,用于求两个数的最大公约数。
设
g
c
d
(
a
,
b
)
gcd(a, b)
gcd(a,b) 表示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),当
a
%
b
=
=
0
a\%b == 0
a%b==0时,
b
b
b就是最大公约数。
将以上式子转化为递归代码就是欧几里得算法。
欧几里得算法代码:
public static int gcd(int a, int b){
if(b == 0){ // 当b==0,说明上一层的a%b == 0,所以直接返回a.
return a;
}
return gcd(b, a%b);
}
那么什么是扩展欧几里得算法呢?
扩展欧几里得算法
扩展欧几里得算法是对欧几里得算法的扩展,用于求解二元一次方程的整数解。
欧几里得算法是求公约数的,跟二元一次方程能扯上什么关系?
这个关系就是 裴蜀定理:
若a,b是整数,且gcd(a,b)=d,那么对于任意的整数x,y,ax+by都一定是d的倍数,特别地,一定存在整数x,y,使ax+by=d成立
简单来说就是,如果两个整数的最大公约数是d, 那么方程 a x + b y = d ax+by=d ax+by=d一定有解。这是已经被前人证明过的定理,所以我们直接拿来用就好了,不必纠结证明过程。
那么我们知道了
a
x
+
b
y
=
d
ax + by = d
ax+by=d这个式子后怎么求解
x
x
x和
y
y
y呢。
x
和
y
x和y
x和y有很多组解,我们可以知道一组特解是:当
a
%
b
=
=
0
a\%b == 0
a%b==0时,
a
=
=
d
a==d
a==d,此时令
x
=
1
,
y
=
0
x = 1, y = 0
x=1,y=0,得到: a * 1 + 0 = d, 显然,x = 1, y = 0,是ax + by = d的一组特解。
那么当
a
%
b
!
=
0
a\%b!=0
a%b!=0时怎么办?
因为
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),它们之间是存在一定联系的,所以我们不妨试试能否利用这个式子得到通解。
已知:
a
∗
x
1
+
b
∗
y
1
=
g
c
d
(
a
,
b
)
a*x_1 + b*y_1 = gcd(a,b)
a∗x1+b∗y1=gcd(a,b)
b
∗
x
2
+
(
a
%
b
)
∗
y
2
=
g
c
d
(
b
,
a
%
b
)
b*x_2 + (a\%b)*y_2 = gcd(b, a\%b)
b∗x2+(a%b)∗y2=gcd(b,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);
显然:
a
∗
x
1
+
b
∗
y
1
=
b
∗
x
2
+
(
a
%
b
)
∗
y
2
a*x_1 + b*y_1 = b*x_2 + (a\%b)*y_2
a∗x1+b∗y1=b∗x2+(a%b)∗y2, 接下来想办法消掉
%
\%
%号,统一为常见的乘法,以便合并同类项。
因为:
a
%
b
=
a
−
b
∗
(
a
/
b
)
a\%b = a - b*(a/b)
a%b=a−b∗(a/b)
所以:
a
∗
x
1
+
b
∗
y
1
=
b
∗
x
2
+
(
a
−
b
∗
(
a
/
b
)
)
∗
y
2
a*x_1 + b*y_1 = b*x_2 + (a - b*(a/b))*y_2
a∗x1+b∗y1=b∗x2+(a−b∗(a/b))∗y2
将上式展开:
a
∗
x
1
+
b
∗
y
1
=
b
∗
x
2
+
a
∗
y
2
−
b
∗
(
a
/
b
)
∗
y
2
a*x_1 + b*y_1 = b*x_2 + a*y2 - b*(a/b)*y2
a∗x1+b∗y1=b∗x2+a∗y2−b∗(a/b)∗y2
将上式整理:
a
∗
x
1
+
b
∗
y
1
=
a
∗
y
2
+
b
∗
(
x
2
−
(
a
/
b
)
∗
y
2
)
a*x_1 + b*y_1 = a*y2+ b*(x2-(a/b)*y2)
a∗x1+b∗y1=a∗y2+b∗(x2−(a/b)∗y2)
显然:
x
1
=
y
2
,
y
1
=
x
2
−
(
a
/
b
)
∗
y
2
x1 =y2, y1 = x2-(a/b)*y2
x1=y2,y1=x2−(a/b)∗y2
于是我们就得到了解之间的关系,接下来通过一组特解,就可以得到其他解。
我们知道,当
a
%
b
=
=
0
a\%b ==0
a%b==0时,
a
x
+
b
y
=
d
ax+by = d
ax+by=d的解是
x
=
1
,
y
=
0
x = 1, y = 0
x=1,y=0。
而求解
g
c
d
(
a
,
b
)
gcd(a, b)
gcd(a,b)时, 当
a
%
b
=
=
0
a\%b==0
a%b==0时,是递归出口,之后递归函数便会一层一层返回,我们正好可以利用这个性质,求解每一层
a
∗
x
+
b
∗
y
=
d
a*x+b*y = d
a∗x+b∗y=d的解。
代码:
static int x, y;
public static int exgcd(int a, int b){
if(b == 0){
x = 1;
y = 0;
return a;
}
int d = exgcd(b, a%b);
int t = y;
y = x - a/b*y;
x = t;
return d;
}
因为这是一个递归函数,所以当执行到
b
=
=
0
b == 0
b==0时,函数开始一层层返回,最底层是
a
=
d
,
x
=
1
,
y
=
0
a = d, x = 1, y = 0
a=d,x=1,y=0,我们可以在函数返回的过程中依次求出每一层的解,当函数执行完毕时, 我们就可以得到原式的一个特解。
何时无解:
根据定义,对于
a
∗
x
+
b
∗
y
=
d
,
如
果
d
是
g
c
d
(
a
,
b
)
a*x+b*y=d, 如果d是gcd(a,b)
a∗x+b∗y=d,如果d是gcd(a,b)的倍数,则该式有解,所以如果
d
d%gcd(a,b) != 0
d,则该式无解。
最小正整数解
a ∗ x + b ∗ y = d a*x+b*y = d a∗x+b∗y=d是有很多组解的,我们怎么得到x的最小整数解呢?
设
(
x
1
,
y
1
)
,
(
x
2
,
y
2
)
是
a
∗
x
+
b
∗
y
=
d
的
两
组
解
(x_1,y_1), (x_2, y_2)是a*x+b*y=d的两组解
(x1,y1),(x2,y2)是a∗x+b∗y=d的两组解
则有:
a
∗
x
1
+
b
∗
y
1
=
a
∗
x
2
+
b
∗
y
2
a*x_1 + b*y_1 = a*x_2 + b*y_2
a∗x1+b∗y1=a∗x2+b∗y2
整理:
a
∗
(
x
1
−
x
2
)
=
b
∗
(
y
2
−
y
1
)
a*(x_1-x_2) = b*(y_2-y_1)
a∗(x1−x2)=b∗(y2−y1)
等式两边同时除以gcd(a,b):
得到:
a
/
g
c
d
(
a
,
b
)
∗
(
x
1
−
x
2
)
=
b
/
g
c
d
(
a
,
b
)
∗
(
y
2
−
y
1
)
a/gcd(a,b)*(x_1-x_2) = b/gcd(a,b)*(y_2-y_1)
a/gcd(a,b)∗(x1−x2)=b/gcd(a,b)∗(y2−y1)
设
a
′
=
a
/
g
c
d
(
a
,
b
)
,
b
′
=
b
/
g
c
d
(
a
,
b
)
a' = a/gcd(a,b), b'= b/gcd(a,b)
a′=a/gcd(a,b),b′=b/gcd(a,b)
显然:
a
′
与
b
′
互
质
a'与b'互质
a′与b′互质
所以对于:
a
′
∗
(
x
1
−
x
2
)
=
b
′
∗
(
y
2
−
y
1
)
a'*(x_1-x_2) = b'*(y_2-y_1)
a′∗(x1−x2)=b′∗(y2−y1),
(
x
1
−
x
2
)
是
b
′
的
整
倍
数
,
(
y
2
−
y
1
)
是
a
′
的
整
倍
数
(x_1 - x_2)是b'的整倍数,(y_2-y_1)是a'的整倍数
(x1−x2)是b′的整倍数,(y2−y1)是a′的整倍数
就好比
3
∗
10
=
2
∗
15
3* 10 = 2 *15
3∗10=2∗15
所以:
x
1
=
x
2
+
b
′
∗
k
(
k
=
1
,
2
,
3
,
.
.
.
)
x_1 = x_2 + b'*k(k = 1,2,3,...)
x1=x2+b′∗k(k=1,2,3,...)
即
x
的
值
是
每
b
′
一
循
环
,
所
以
直
接
让
x
%
b
′
x的值是每b'一循环,所以直接让x\%b'
x的值是每b′一循环,所以直接让x%b′就可以得到最小的解了,但求出的解可能是负数,所以先让
x
%
b
x\%b
x%b,然后再让
x
x
x加上一个
b
′
b'
b′,然后再对
b
′
b'
b′取余。
即:
x
=
(
x
%
b
′
+
b
′
)
%
b
′
x = (x\%b'+b')\%b'
x=(x%b′+b′)%b′
模板题:
扩展欧几里得算法
代码:
import java.io.*;
import java.util.*;
public class Main{
static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
public static int Int(String s){return Integer.parseInt(s);}
static int x, y;
public static int exgcd(int a, int b){
if(b == 0){
x = 1;
y = 0;
return a;
}
int d = exgcd(b, a%b);
int t = y;
y = x - a/b*y;
x = t;
return d;
}
public static void main(String[] args) throws Exception{
int n = Int(in.readLine());
while(n --> 0){
String[] s = in.readLine().split(" ");
int a = Int(s[0]);
int b = Int(s[1]);
exgcd(a, b);
out.write(x+" "+y+"\n");
}
out.flush();
}
}
例题:
线性同余方程
代码:
import java.io.*;
import java.util.*;
public class Main{
static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
public static int Int(String s){return Integer.parseInt(s);}
static int x, y;
public static int exgcd(int a, int b){
if(b == 0){
x = 1;
y = 0;
return a;
}
int d = exgcd(b, a%b);
int t = y;
y = x - a/b*y;
x = t;
return d;
}
public static void main(String[] args) throws Exception{
int n = Int(in.readLine());
while(n --> 0){
String[] s = in.readLine().split(" ");
int a = Int(s[0]);
int b = Int(s[1]);
int m = Int(s[2]);
int d = exgcd(a, m);
int c = b/d;
int t = m/d;
if(b % d != 0){
out.write("impossible\n");
}
else out.write(((long)x*c)%t + "\n");
}
out.flush();
}
}